W poprzednim artykule pokazałem jak tworzyć klasyczny szablon projektów dla Visual Studio. Natomiast z tego artykułu dowiesz się jak stworzyć szablon dla dotnet new. Taki szablon możesz zaimportować na każdym komputerze i systemie, w którym zainstalowany jest dotnet.
Klasyczne szablony (dla Visual Studio) są obsługiwane tylko przez VisualStudio dla Windows. Natomiast te nowe dla „dotnet new” już nie mają tego ograniczenia. Ponadto pozwalają duuużo prościej zrobić bardziej skomplikowane rzeczy. Minus? Nie da się tego wyklikać…jeszcze…
Pamiętaj, że ten artykuł to nie jest kompletny podręcznik szablonów. O nie! To jest dość duży temat. Artykuł pokazuje jak zacząć i jak zrobić coś trudniejszego niż „hello world”. Na koniec jednak daję Ci kilka linków, które uzupełniają artykuł. Jeśli czegoś tutaj nie znajdziesz, być może znajdziesz w oficjalnej dokumentacji (podlinkowanej na końcu).
Z czego składa się szablon
Są dwie rzeczy:
struktura projektu – wrzucasz tutaj dowolną zawartość (pliki, foldery, inne projekty), która ma się znaleźć na dzień dobry w projekcie wynikowym – od tego zaczynasz, po prostu stwórz nowy projekt, który będzie wyjściowym projektem dla Twojego szablonu
plik konfiguracyjny template.json – opisuje parametry szablonu
opcjonalne pliki np. do obsługi Wizarda, czy też ikonka
W przeciwieństwie do klasycznych szablonów pod VisualStudio, nie musisz stosować specjalnych tokenów w formie parametrów w swoich projektach. Nie ma też problemu z kompilowaniem ich – właśnie ze względu na brak tych tokenów.
Musisz jednak zapewnić odpowiednią strukturę katalogów, np. taką:
Jak widzisz, w katalogu z projektem musisz utworzyć folder .template.config, w którym umieścisz całą konfigurację swojego szablonu. Ja utworzyłem projekt WebApplication (RazorPages), jako projekt wyjściowy, któremu nadałem nazwę WebAppWithApiTemplate.
Tylko uwaga – będzie Cię korciło, żeby dodać folder .template.config do projektu w Visual Studio. Nie rób tego. Wszystko, co zrobisz z projektem będzie miało skutek w Twoim szablonie. W prawdzie można potem zastosować pewien mechanizm, żeby pozbyć się tego folderu w wynikowym projekcie, ale moim zdaniem można to zrobić lepiej…
Dodawanie plików konfiguracyjnych do solucji
To nie znaczy jednak, że nie możesz pracować na plikach konfiguracyjnych w Visualu. Byłoby to mocno uciążliwe. Utwórz zatem ręcznie katalog .template.config, a w nim plik o nazwie template.json. A następnie w Visual Studio kliknij prawym klawiszem myszy na solucję i wybierz Add -> New Solution Folder:
To utworzy Ci folder (tzw. „filtr”) na poziomie solucji. Jednak żaden folder na dysku nie powstanie. Ja swój „filtr” nazwałem template-files – to jak nazwiesz swój nie ma żadnego znaczenia.
Następnie kliknij go prawym klawiszem myszy i wybierz Add -> Existing Item:
W pickerze wybierz pliki konfiguracyjne szablonu, które utworzyłeś wcześniej. To doda je do solucji, ale nie zrobi żadnych zmian na poziomie projektów. Dzięki temu będziesz mógł pracować na plikach konfiguracyjnych w Visual Studio:
Plik template.json
To właściwie serce Twojego szablonu. Niestety na dzień dzisiejszy nie da się go wyklikać, ale dość łatwo się go tworzy. Zwłaszcza, jeśli wykorzystasz możliwości VisualStudio. Spójrz na okno z zawartością pliku:
Plik oczywiście jest pusty, ale w edytorze kodu widzisz combobox oznaczony jako Schema. Możesz w nim wybrać schemat pliku json, który chcesz tworzyć – dzięki temu cały Intellisense zadziała i będziesz mieć eleganckie podpowiedzi w kodzie. Odnajdź na tej liście https://json.schemastore.org/template.json – to jest opisany schemat szablonu.
UWAGA! Widoczny na powyższym screenie schemat to jakiś przykładowy pierwszy lepszy z listy. Pamiętaj, żeby odnaleźć tam konkretny: https://json.schemastore.org/template.json
VS dzięki temu opisowi może dawać Ci podpowiedzi. A teraz zacznijmy wypełniać plik template.json. Na początek trzeba mu wskazać wybrany schemat. Robimy to za pomocą właściwości $schema:
To są minimalne wymagane właściwości. Rozpoznasz je po tym, że w Intellisense są pogrubione. A teraz przelecimy je po kolei:
author – to oczywiście autor szablonu
classifications – to klasyfikuje szablon. Te wartości będą potem używane w filtrach np. Visual Studio. Podajesz tutaj tablicę wartości, które powinny kategoryzować Twój szablon. W VisualStudio to te właściwości podczas dodawania nowego projektu. W narzędziu dotnet new --list będą się pojawiały w rubryce tags.
identity – to jest coś w rodzaju ID Twojego szablonu. To musi być unikalne, dlatego daj tutaj jakąś unikalną wartość, ale niech to będzie coś opisowego a nie np. GUID – później się przyda
name – pełna nazwa Twojego szablonu (przykłady istniejących: „Console App”, „ASP.NET Core Web App”)
shortName – nazwa skrócona szablonu, której będziesz mógł używać, tworząc projekt na jego podstawie
tags – podstawowy opis szablonu – wskazujesz, że szablon odnosi się do projektu (type: project) i jaki jest główny język projektu. Szablon może być jeszcze dla całej solucji (kilka projektów) lub nowym item’em – czyli poszczególnym typem pliku.
Dość istotną właściwością jest sourceName. SourceName to ciąg znaków zarówno w zawartości plików jak i w ich nazwach, który podczas tworzenia projektu zostanie zastąpiony nazwą projektu, którą poda użytkownik. Przykładowo, jeśli mój kod wygląda tak:
I jeśli we właściwości sourceName podam: „WebAppWithApiTemplate” wtedy ten ciąg zostanie zamieniony na to, co wpisze użytkownik podając nazwę swojego projektu.
Czyli zarówno namespace’y, jak i nazwa projektu głównego zostaną zamienione na ten żądany przez użytkownika. A więc dodajmy to:
Pewnie są sytuacje, w których nie chciałbyś takiej podmiany. Wtedy po prostu tego nie stosujesz, gdyż nie jest to właściwość wymagana.
To jest już pełnoprawny projekt szablonu. Teraz musisz go tylko zapakować w nuget i zainstalować. O tym później. Najpierw zrobimy inne fajne rzeczy.
Parametry
Szablon często będzie zawierał jakieś parametry, które mogą być ustawione przez użytkownika. W pliku template.json lądują one we właściwości symbols. Parametry są jednym z typów symboli.
Parametr składa się z nazwy (ID), a także typu danych. Może zawierać wartość domyślną, a także ciąg, który będzie zamieniony na wartość parametru.
Jak to działa? Silnik szablonów analizuje plik template.json. Jeśli teraz znajdzie jakieś parametry (symbole) do podmiany, to każdy taki ciąg znaków w kodzie zamieni na konkretną wartość podaną przez użytkownika. Analogicznie do właściwości sourceName.
Dodajmy do naszego szablonu folder ApiClient, a nim prostą klasę:
I teraz chcę, żeby taka klasa znalazła się w katalogu ApiClient, ale to użytkownik będzie decydował o tym, jak ten klient ma się nazywać. Dlatego też posłużę się symbolem:
"symbols": {
"ApiClientName": { //nazwa parametru (jego ID)
"type": "parameter",
"datatype": "text",
"description": "Podaj nazwę klasy dla swojego klienta API",
"displayName": "Nazwa klasy dla Api Client",
"defaultValue": "MyApiClient",
"isRequired": true,
"replaces": "varApiClientName",
"fileRename": "varApiClientName"
}
}
Uznaj właściwość symbols za klasę, która posiada inne właściwości – konkretne symbole. I teraz tak. Każdy symbol ma swoją nazwę (ID). Tutaj to APIClientName. Nazwą symbolu możesz posługiwać się w kodzie swojego szablonu, o tym za chwilę.
Każdy symbol to też swego rodzaju klasa, która posiada konkretne właściwości:
type – typ symbolu. To może być parametr, generator itd. Na razie skupmy się na parametrach
datatype – typ danych, jakie ten parametr będzie przechowywał. Inne typy to np:
bool – true/false – na GUI równoznaczne z checkboxem (np. Use Https)
choice – lista wartości do wyboru – na GUI równoznaczne z combo (np. wybór Identity)
float, hex, int, text – no te wartości same się opisują. Przy czym text jest wartością domyślną
description – opis parametru. Pokaże się w konsoli w poleceniu dotnet new, ale też w VisualStudio jako dodatkowa informacja (chociaż dla IDE jest dodatkowy/osobny sposób pokazywania parametrów opisany niżej)
defaultValue – wartość domyślna
isRequired – czy parametr jest wymagany. Jeśli podczas tworzenia projektu przez polecenie dotnet new użytkownik nie poda tej wartości, to dotnet new zwróci błąd
replaces – to jest ten magiczny string w plikach, który zostanie zamieniony na wartość wprowadzoną przez użytkownika. U mnie to varApiClientName. Ten prefix „var” jest tylko pewnym udogodnieniem dla mnie. Nie ma tutaj żadnych wytycznych ani obostrzeń. Ale pamiętaj, że zostaną zamienione WSZYSTKIE STRINGI w kodzie. Łącznie z tymi w plikach projektów, komentarze, a nawet wszystkie wartości tekstowe. Jeśli więc miałbym w kodzie coś takiego:
public string Id { get; set; } = "varApiClientName";
to też będzie zamienione. Dlatego ja posługuję się tym przedrostkiem var. To trochę chroni przed zrobieniem głupoty.
fileRename – analogicznie jak replace z tą różnicą, że taki ciąg zostanie podmieniony w nazwach plików.
Stwórzmy teraz bardzo prostego i prymitywnego klienta dla API. To jest tylko bardzo prymitywny przykład. Jak fajnie zrobić takiego klienta pisałem w tym artykule. Nie chcę zaciemniać obrazu, dlatego zrobię najprościej jak się da. Ten klient powstał tylko dla przykładu. I naprawdę nie jest istotne, że nie ma większego sensu 😉
Sparametryzowany klient
Co chcemy uzyskać?
Użytkownik ma mieć opcję do podania nazwy klienta – to już nam załatwił parametr varApiClientName.
Użytkownik ma mieć możliwość wyłączenia lub włączenia rejestracji serwisów – czyli domyślnej konfiguracji klienta.
W przypadku włączenia rejestracji serwisów, użytkownik ma mieć możliwość podania BaseAddress dla API. Zarówno dla środowiska produkcyjnego jak i deweloperskiego.
Tworzenie pełnego kodu
Tak, czy inaczej musimy stworzyć cały kod. W pliku template.json nie możemy dodawać plików ani linijek kodu. Dlatego też musimy wyjść od pełnego obrazu.
Przede wszystkim jest klasa, która umożliwia wysyłanie żądań – prosty klient w pliku varApiClientName.cs:
namespace WebAppWithApiTemplate.ApiClient
{
public class varApiClientName
{
private readonly HttpClient _client;
public varApiClientName(HttpClient client)
{
_client = client;
}
public async Task<HttpResponseMessage> GetData(string endpoint)
{
return await _client.GetAsync(endpoint);
}
public async Task<HttpResponseMessage> PostData<TOut>(TOut data, string endpoint)
{
return await _client.PostAsJsonAsync(endpoint, data);
}
}
}
Do tego trzeba zrobić rejestrację HttpClienta. Dlaczego w taki sposób? Opisywałem to w tym artykule.
W folderze ApiClient dodałem plik ServiceCollectionsExtensions.cs:
public class varApiClientNameOptions
{
public const string CONFIG_SECTION = "varApiClientName";
public string BaseAddress { get; set; }
}
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddvarApiClientNameIntegration(this IServiceCollection services,
IConfiguration config)
{
varApiClientNameOptions options = new varApiClientNameOptions();
config.Bind(varApiClientNameOptions.CONFIG_SECTION, options);
services.AddHttpClient<varApiClientName>(client =>
{
client.BaseAddress = new Uri(options.BaseAddress);
});
return services;
}
}
W tym kodzie po prostu rejestrujemy HttpClient dla naszego klienta. Jeśli nie wiesz co robi AddHttpClient, koniecznie przeczytaj ten artykuł.
Najpierw pobieramy ustawienia, w których będzie wpisany adres bazowy api. To nam zapewnia, że adres bazowy będzie pobrany w zależności od środowiska. Nasępnie konfigurujemy klienta.
Spójrz w jaki sposób przemycam wszędzie varApiClientName – te wszystkie miejsca zostaną zamienione na faktyczną nazwę klienta.
Na koniec wywołamy jeszcze tę metodę podczas rejestracji serwisów w pliku Program.cs:
using WebAppWithApiTemplate.ApiClient;
//...
builder.Services.AddRazorPages();
builder.Services.AddvarApiClientNameIntegration(builder.Configuration);
Dodawanie parametrów
W związku z tym, że chcemy aby użytkownik mógł podać adres bazowy dla API, musimy dodać takie parametry do template.json. Oczywiście dodajemy to dalej w sekcji symbols:
"ApiProdBaseAddress": {
"type": "parameter",
"datatype": "text",
"description": "Bazowy adres dla API dla środowiska produkcyjnego",
"replaces": "varApiProdBaseAddress",
"defaultValue": "https://api.example.com/"
},
"ApiDevBaseAddress": {
"type": "parameter",
"datatype": "text",
"description": "Bazowy adres dla API dla środowiska deweloperskiego",
"replaces": "varApiDevBaseAddress",
"defaultValue": "https://api.dev.example.com"
}
Dodałem dwa parametry – jeden to adres bazowy dla środowiska produkcyjnego, drugi dla deweloperskiego. Jeśli nie wiesz, czym jest appsettings.json, czym się różni od appsettings.Development.json i masz małe pojęcie o konfiguracji .NET, koniecznie przeczytaj ten artykuł.
Dodawanie ustawień w aplikacji
OK, możemy teraz te dane dodać do plików appsettings.
Spójrz w jaki sposób przemycam tutaj znów nazwy tego klienta i parametr z bazowym adresem dla API. Analogicznie zrobimy w pliku appsettings.Development.json:
OK, teraz mamy ogarnięte punkty 1 i 3 z naszych założeń. Czyli użytkownik może podać nazwę klienta, a także adresy bazowe do API. To teraz musimy się zatroszczyć o punkt 2 – użytkownik ma mieć możliwość wyłączenia jakiejkolwiek konfiguracji klienta.
Najpierw dodajmy taki parametr w pliku template.json:
"ConfigureApiClient": {
"type": "parameter",
"datatype": "bool",
"description": "Czy klient API ma być domyślnie skonfigurowany",
"defaultValue": "true"
}
Zauważ, że tutaj nie posługuję się właściwością replaces, ponieważ niczego nie będziemy podmieniać. Albo wykonamy fragment kodu, albo nie.
Zatem wytnijmy warunkowo fragment kodu, który wywołuje rejestrację klienta, czyli to:
using WebAppWithApiTemplate.ApiClient;
//...
builder.Services.AddRazorPages();
builder.Services.AddvarApiClientNameIntegration(builder.Configuration);
Robimy to w bardzo prosty sposób – dyrektywami kompilatora:
W taki sposób możesz sterować fragmentami kodu w plikach. Po prostu sprawdzasz, czy parametr o podanym ID ma wartość true.
Usuwanie plików
Jeśli chodzi jednak o plik ServiceCollectionExtensions.cs nie jest on w ogóle potrzebny, gdy użytkownik nie chce automatycznej konfiguracji. Nie ma sensu wycinać kodu w tym pliku, bo zostałby nam zupełnie pusty. Dlatego warunkowo możemy go usunąć. Z pomocą przychodzi nowa sekcja w pliku template.json – sources – w niej możemy stosować modyfikatory plików źródłowych.
Sekcja sources zawiera modyfikatory. Każdy modyfikator może mieć warunek lub wykonać się bezwarunkowo (zawsze). Napiszmy więc taki modyfikator, który usunie plik ServiceCollectionsExtensions.cs, gdy parametr ConfigureApiClient nie będzie ustawiony (jego wartość będzie na false):
Jak widzisz, żeby sprawdzić wartość jakiegoś parametru, po prostu wpisujemy jego nazwę w nawias. Możemy też go zanegować wykrzyknikiem. Czyli w tym przypadku, gdybyśmy chcieli ten warunek przepisać na kod, wyglądałoby to akoś tak:
Następnie musimy wskazać, co ma się zadziać, jeśli warunek będzie spełniony. A więc pozbywamy się pliku, wykluczamy go (exclude) z całości.
Teraz może pojawić się pytanie – skąd silnik template’ów wie, gdzie jest konkretny plik. Ścieżką wyjściową (bazową) dla silnika jest folder, w którym znajduje się folder .template.config.
I tutaj odnosimy się do folderu ApiClient i pliku, który się w nim znajduje. Możesz stosować tutaj symbole wieloznaczne, np: „**/*Extensions.cs” – usunęłoby wszystkie pliki z wszystkich podkatalogów, których nazwy kończą się na Extensions.cs.
Jeśli jednak chciałbyś wykluczyć większość plików, a zostawić tylko jeden, łatwiej będzie posłużyć się modyfikatorem include. Domyślnie include włącza wszystkie pliki, które znajdują się w projekcie (oczywiście poza katalogiem .template.config).
Możesz też chcieć warunkowo zmienić nazwy plików. Do tego możesz zastosować modyfikator rename.
Warunkowe zawartości plików
Pliki *.cs
Jak już pisałem wcześniej, w plikach cs możemy posługować się dyrektywami kompilatora, żeby warunkowo mieć jakąś zawartość, np:
Możesz stosować również dyrektywę #elif. A co jeśli chcesz jednak, żeby jakaś dyrektywa była widoczna na koniec w pliku? Np:
#if DEBUG
logger.LogWarning("UWAGA! Tryb deweloperski!");
#endif
Jest i na to sposób. Nazywa się to processing flag. To już jednak wygląda jak czary:
//-:cnd:noEmit
#if DEBUG
logger.LogWarning("UWAGA! Tryb deweloperski!");
#endif
//+:cnd:noEmit
No cóż… Grunt, że jest taka opcja 😉 Pamiętaj – „processing flag”.
Pliki JSON
Tutaj nie możemy się posłużyć bezczelnie dyrektywą, ale możemy posłużyć się specjalnym komentarzem. W plikach json komentarz jest rozpoczynany znakami // i trwa do końca linii. To wytnijmy teraz ustawienia związane z konfiguracją api clienta z plików appsettings:
To spowoduje dokładnie to, czego się spodziewasz – sekcja varApiClientName pojawi się w pliku appsettings tylko wtedy, gdy parametr ConfigureApiClient będzie miał wartość TRUE. Zrób analogiczną operację w pliku appsettings.Development.json.
Zmiany w plikach projektów i innych XMLach
Tutaj analogicznie posłużymy się komentarzami XMLowymi. Możesz bez problemu je stosować w plikach projektów. Dodajmy do naszych wymagań jeszcze jedno – niech użytkownik wybierze, czy chce korzystać z biblioteki System.Text.Json, czy ze starego dobrego Newtonsoft.Json. Na początek dodajmy taki parametr do pliku template.json. Niech to będzie combobox.
Jak widzisz, choices składa się z tablicy obiektów (choice, description, displayName). Ich właściwości same się opisują. I co ciekawe, przy choice też możesz stosować właściwość replaces, co później pokażę. Pamiętaj też, żeby wartością datatype było ustawione na choice.
To teraz zmieńmy plik projektu. Jak już wspomniałem robimy to specjalnym komentarzem:
Pomiędzy znakiem komentarza <!-- a hashem nie może być spacji. Inaczej silnik szablonów nie uzna tego za dyrektywę, tylko za zwykły komentarz.
Wartość, którą porównujesz musi być w apostrofach. Inaczej warunek nie zostanie uznany jako spełniony.
Można to zrobić jeszcze inaczej – bez porównywania konkretnych wartości. Przydać się to może w sytuacji, gdzie miałbyś do sprawdzenia kilka tych samych warunków w kilku miejscach. Ale o tym za chwilę.
Trochę magii – czyli czego nie powie Ci dokumentacja
Dokumentacje szablonów są całkiem nieźle opisane, ale mają miejscami sporo braków. Jak np. magiczny parametr Framework. Na jakimś video z Microsoftu słyszałem, że jest on zalecany, ale nigdzie nie jest opisany. Hurrra!
Generalnie parametr Framerowk daje wybór frameworka, na którym ma być stworzony projekt oparty na szablonie. Tworzy się go analogicznie jak inne parametry:
wartości w choice muszą być dokładnymi „monikerami” wersji .NET, np.:
net48 – .NetFramework 4.8
netstandard2.1
netcoreapp3.1
net5.0
net6.0
net7.0
Jeśli wrzucisz inne wartości, to magiczny mechanizm nie zadziała.
we właściwości replaces wstawiasz, jak to z innymi parametrami, string do podmiany. Pamiętaj, że to podmieni wszystkie znalezione stringi „net6.0” na wybraną przez użytkownika wartość – w szczególności w pliku projektu.
Symbole wyliczane
Innym typem symbolu są symbole wyliczane. To coś w rodzaju parametru. Tylko nie podaje go użytkownik, a wyliczamy go na jakiejś podstawie. Możesz to stosować gdy w kilku miejscach stosujesz jakieś warunki. Np:
Taki kodzik stworzy Ci coś w rodzaju zmiennej o nazwie IsLTS, która przyjmie odpowiednią wartość na podstawie innych parametrów. W tym konkretnym przypadku możesz sprawdzić, czy wybrana wersja .net jest długowieczną (long time support), czy też nie. Później na tej podstawie możesz zadecydować o czymś innym, stosując w innych warunkach zmienną IsLTS – dokładnie w taki sam sposób jak inne parametry. Np. możesz gdzieś w kodzie wpisać warning:
#if (!IsLTS)
#warning "Caution! Your framework is not LTS version!"
#endif
UWAGA! Symbole typu computed mogą przyjmować jedynie wartości typu bool.
Instalowanie szablonu
Właściwie wszystkie wytyczne mamy już ogarnięte. Teraz możemy zainstalować taki szablon. Oczywiście nie można zrobić z VisualStudio tego automatem… jeszcze… za to można to zrobić na kilka sposobów. Najpierw podam Ci sposób lokalny – to jest wystarczające jeśli tworzysz jakiś szablon dla siebie i raczej nie będziesz w nim już grzebał.
Uruchom terminal i przejdź do katalogu głównego Twojej aplikacji. Tam, gdzie masz katalog z projektem. U mnie plik projektu znajduje się w projekty\MasterBranch\SingleNewTemplate\src\WebAppWithApiTemplate\WebAppWithApiTemplate.csproj dlatego muszę ustawić się w katalogu src: projekty\MasterBranch\SingleNewTemplate\src\
Teraz wystarczy zainstalować szablon, wykonując polecenie:
dotnet new --install .\WebAppWithApiTemplate
czyli podajemy nazwę katalogu, w którym jest projekt z szablonem.
Następnie możemy przejrzeć sobie listę szablonów:
dotnet new --list
U mnie wygląda to tak:
Jak widzisz, mój szablon został zainstalowany.
Jeśli będziesz chciał go odinstalować, to dotnet new podpowie Ci dokładnie co zrobić, ale zasadniczo powinieneś podać pełną ścieżkę dostępu do katalogu z projektem szablonu, np.:
dotnet new --uninstall d:\projekty\MasterBranch\SingleNewTemplate\src\WebAppWithApiTemplate
Jeśli chciałbyś utworzyć sobie projekt na podstawie tego szablonu, to możesz to zrobić podając króką nazwę szablonu. A wywołując instrukcję help, dostaniesz pełną pomoc dla swojego szablonu:
dotnet new waapi --help
Jak widzisz, dotnet świetnie sobie poradził z Twoimi parametrami i pokazuje Ci dokładnie jak ich użyć. Przykładowe utworzenie projektu:
dotnet new waapi -J Default -A ExampleApiClient -o .\NewProject
Tutaj jednak pamiętaj, że parametry polecenia dotnet new mieszają się z Twoimi parametrami. Stąd np. -o (--output) – parametr dotnet new, który mówi gdzie stworzyć projekt.
Możesz też taki projekt stworzyć bezpośrednio z Visual Studio (zrestartuj Visual Studio po zainstalowaniu szablonu).
Instalowanie szablonu NuGetem
Ten sposób umożliwia Ci podzielenie się szablonem z innymi, a także łatwe jego wersjonowanie. Wymaga to utworzenia nugetowej paczki. Ale zadanie jest dość proste.
Utwórz gdzieś plik template.nuspec. Możesz to zrobić w katalogu .template.config, ale nie musisz. Utwórz go gdzieś, gdzie uznasz za słuszne. Jego przykładowa zawartość powinna wyglądać tak:
Ten plik sam się opisuje. Musisz pamiętać jedynie o tym, żeby sekcja files odnosiła się do konkretnych plików Twojego szablonu. Ja akurat plik nuspec umieściłem w .template.config – dlatego też w sekcji files idę po pliki „piętro wyżej”.
Następnie musisz wywołać na nim:
nuget pack template.nuspec
Jeśli wszystko jest ok, to otrzymałeś plik z rozszerzeniem nupkg. I to jest Twój pakiet, którym możesz się już dzielić. Żeby teraz zainstalować taki szablon, wystarczy:
dotnet new install .\twoj-plik.nupkg
Odinstalowanie jest analogiczne. Tyle, że podajesz tylko nazwę pakietu – bez ścieżki do niego.
Przygotowanie szablonu dla środowisk IDE
dotnetcli.host.json
Oprócz pliku template.json, który jest podstawą, możesz mieć jeszcze dodatkowe pliki, w których swój szablon możesz podkręcić. Najprostszym jest dotnetcli.host.json. Jego rolą jest ustawienie aliasów do parametrów w narzędziu dotnet new. Domyślnie są one tworzone jakoś automagicznie. Jeśli wyświetlisz pomoc dla swojego szablonu, zobaczysz taki mniej więcej opis:
Parametr na długą nazwę, np. --ApiClientName, ale może mieć też krótki alias, np: -a. Zauważ, że pełna nazwa parametru rozpoczyna się podwójnym myślnikiem [–], natomiast aliast pojedynczym [-]. Jest to naturalne zachowanie. Jak widzisz powyżej, dla każdego parametru istnieją jakieś domyślne aliasy. Ale możesz je sam utworzyć, stosując plik dotnetcli.host.json, np. tak:
Najpierw podajesz id symbolu takie jak w pliku template.json. Następnie masz parametry:
longName – długa nazwa – czyli ta prawilna cała nazwa parametru prefiksowana podwójnym myślnikiem
shortName – alias – krótka nazwa parametru. Jeśli podasz pusty string „”, wtedy parametr nie będzie miał aliasu.
isHidden – jeśli ustawisz na true, to ta opcja nie będzie widoczna z poziomu dotnet new --help. Jednak nadal będziesz mógł z niej korzystać (po prostu nie zobaczysz jej w podpowiedziach).
ide.host.json
Ten plik z kolei pomaga ogarnąć dodatkowe rzeczy w Visual Studio (i pewnie w innych IDE). We wcześniejszych wersjach (poniżej 2022) był wymagany. Teraz jest opcjonalny, jednak umożliwia dodatkowe czary mary.
Tutaj ustawiamy ikonkę dla szablonu. To zakłada, że w katalogu .template.config masz ikonkę o nazwie icon.png. Żeby ikonka była dobrze widoczna, powinna być w rozmiarze 32×32 piksele o głębokości 32 bitów.
UWAGA! Jeśli tego nie zrobisz, ale w katalogu .template.config będziesz miał ikonkę o nazwie icon.png, to VisualStudio od wersji 2022 też to ogarnie.
Parametry
Informacje o parametrach przechowywane są we właściwości symbolInfo. To po prostu tablica parametrów, mówiących VisualStudio jak ma je obsługiwać. Podobnie jak w template.json
Podstawowa budowa obiektu jest taka:
"symbolInfo": [
{
"id": "ApiClientName",
"name": {
"text": "Nazwa klienta API"
},
"description": {
"text": "Podaj nazwę klienta dla swojego API. Tak będzie nazywać się wygenerowana klasa."
},
"isVisible": true
}
]
Id to nazwa parametru z pliku template.json. Właściwości name i description pokażą się przy tym parametrze jako jego nazwa i opis, który będzie w hincie:
Jeśli nie wypełnisz właściwości name i description, zostanę one odczytane z pliku template.json.
Parametr isVisible określa, czy właściwość ma być widoczna.
Jeśli używasz pliku ide.host.json, to pamiętaj że domyślnie wszystkie parametry są UKRYTE. Stąd właściwość isVisible. Ustawiasz nią, które parametry mają być widoczne. Jeśli chcesz żeby wszystkie były widoczne, to jest od tego właściwość defaultSymbolVisibility, np:
Ten kod sprawi, że wszystkie parametry z template.json będą widoczne podczas tworzenia nowego projektu w Visual Studio. Czyli zasadniczo w VisualStudio 2022 równie dobrze ten plik (ide.host.json) mógłby nie istnieć.
Plik ide.host.json ma jeszcze kilka parametrów:
description – opis szablonu – jeśli go nie wypełnisz, zostanie wzięty z pliku template.json
name – nazwa szablonu – analogicznie jak wyżej
order – kolejność w jakiej pojawi się szablon na liście tworzenia nowego projektu
supportsDocker – jeśli ustawione na true, to podczas tworzenia projektu zobaczysz opcję (checkbox) pozwalającą zdokeryzować taki projekt. I tutaj uwaga! Jeśli chcesz żeby ta opcja była widoczna w VisualStudio, musisz do pliku template.json dodać parametr Framework (ten magiczny). Inaczej nie zadziała.
unsupportedHosts – możesz tutaj podać listę wersji, dla których ten szablon ma być niewidoczny na dialogu tworzenia nowego projektu, np. taka konfiguracja ukryje szablon w VisualStudio:
"unsupportedHosts": [
{
"id": "vs"
}
]
Zaawansowane
Specjalne operacje
Jeśli silnik szablonów nie potrafi czegoś zrobić standardowo, to być może da Ci taką możliwość za pomocą specjalnych operacji. Te operacje pozwalają na zdefiniowanie dodatkowych akcji podczas tworzenia projektu na podstawie szablonu. Akcje mogą być globalne (dla wszystkich plików) lub ograniczone tylko do niektórych plików.
{
"customOperations": { //odnosi się do wszystich plików - globalnie
},
"specialCustomOperations": { //tylko do niektórych plików
}
}
Ja w jednym swoim szablonie potrzebowałem mieć różne grupy usingów. Szablon został utworzony pod konkretną solucję, w której te specjalne projekty (namespacey) już są. Jednak z nimi projekt szablonu się nie kompilował. Owszem, mogłem utworzyć jakiś plik z konkretnymi namespaceami i potem go usuwać, ale wpadłem na inny pomysł. Wyobraź sobie taki plik *.cs:
/*add-usings
using Nerdolando.BS.Common.Abstractions;
using Nerdolando.BS.Integrations;
add-usings*/
namespace WebAppWithApiTemplate.ApiClient
{
public class SpecialOperations
{
}
}
Zauważ, że usingi mam tutaj wykomentowane. Ale chcę, żeby pojawiły się w wynikowym projekcie. Dlatego posłużyłem się customową operacją z pliku template.json:
Najpierw podaję do jakich plików odnoszą się akcje. Tutaj – wszystkie pliki z rozszerzeniem *.cs. Następnie definuję operacje. Jest kilka predefiniowanych operacji, m.in. „replacement„. Każda z takich operacji ma swoją konfigurację. Niestety na listopad 2022 w schemacie template.json nie ma tego zdefiniowanego, więc nie będziesz miał podpowiedzi w Intellisense.
Dzięki za przeczytanie tego artykułu. Jeśli znalazłeś jakiś błąd albo czegoś nie rozumiesz, koniecznie daj znać w komentarzu. Daj też znać w komentarzu, jeśli uważasz, że taki artykuł jest za długi i powinien zostać podzielony na dwa 🙂
Dzisiaj temat będzie całkiem ciekawy. Utworzymy szablon projektu w Visual Studio. Po co to komu? Załóżmy, że często rozpoczynasz projekt od tego samego albo podobnego kodu. To idealny moment, żeby zrobić z tego szablon.
Na przykład ja. Czasem chcę stworzyć aplikację konsolową, ale z całym dobrodziejstwem .NetCore. Jak to zrobić pisałem w tym artykule. Jednak ciągłe powtarzanie tych samych czynności strasznie mnie denerwuje. Więc postanowiłem zgłębić temat i utworzyć szablon. Teraz wystarczy, że tworząc nowy projekt, nie będę wybierał Console Application, tylko mój własny szablon, w którym już jest zaszyte wszystko.
Zapraszam w podróż 🙂
Rodzaje szablonów
Mamy dwa rodzaje szablonów. Szablon pliku (item template) – to może być np. plik xml, json, plik zasobów itd. To są wszystko „item templates”.
Szablon projektu – tym się zajmujemy w tym artykule. To szablon całego projektu. Np. Console Application, czy też WebApi. To są wszystko „project templates”.
Szablon Visual Studio vs szablon .NET
Zasadniczo szablon można zrobić na dwa zupełnie różne sposoby.
Szablon dla Visual Studio
Jest prostszy w zrobieniu, zwłaszcza jeśli chcesz zrobić go szybko i bez zbędnych dupereli (okienek). Jednak zadziała tylko na VisualStudio pod Windowsem. To jest, można powiedzieć, klasyczna wersja. W tym artykule właśnie nim się zajmiemy. Zakłada utworzenie pliku .vstemplate.
Szablon pod .NET (Core)
Nowszą możliwością jest zrobienie szablonu pod .NET(Core). Taki szablon jest uniwersalny. Możesz zaimportować go do Visual Studio na Windows i na MACu. Możesz zaimportować go również do innych IDE, które mają takie wsparcie (jeśli znasz jakąś listę, podziel się w komentarzu). Zakłada utworzenie odpowiedniego katalogu i pliku .json. Bardzo uławia też tworzenie Wizarda.
Z czego składa się szablon
Jak wspomniałem, w tym artykule zajmiemy się tworzeniem szablonu „po staremu”. Wkrótce napiszę jak się robi szablon dla .NET.
Szablon zasadniczo składa się z dwóch części:
plików projektów, kodu źródłowego, plików zasobów
metadanych, które są przechowywane w pliku *.vstemplate.
Potem to wszystko jest pakowane do pliku *.zip i jest umieszczone w odpowiednim folderze (Dokumenty\Visual Studio 2022\Templates\ProjectTemplates).
I teraz pierwsza uwaga. Inaczej tworzy się szablon zawierający jeden projekt, a inaczej szablon z wieloma.
Najprostsze podejście
Szablon można bardzo łatwo i szybko utworzyć, wykorzystując funkcję Export Template w Visual Studio. Po prostu:
utwórz nowy projekt – bazę dla Twojego szablonu
zrób wymagane zmiany (dodaj, usuń, zmodyfikuj kod i pliki)
z menu głównego wybierz Project -> Export template
Przez resztę kroków przeprowadzi Cię zmyślny Wizard. Będziesz mógł dodać ikonkę do swojego szablonu, opis itd.
I to w zasadzie tyle. Ta metoda zapisze Twój szablon w odpowiednim miejscu, automatycznie go zaimportuje (jeśli nie odznaczyłeś tej opcji) i będziesz mógł korzystać z niego, tworząc nowy projekt. To jest najszybsza i najprostsza metoda.
Po takim utworzeniu szablonu, w katalogu z szablonami (Documenty\Visual Studio 2022\Templates\ProjectTemplates) zobaczysz plik *.zip o nazwie swojego szablonu. W środku znajdziesz wszystkie pliki, które dodałeś do niego, ale również kilka innych – dodanych przez Wizarda. Między innymi plik *.vstemplate, który w moim przypadku wygląda tak:
I teraz uwaga. Jeśli podejrzysz sobie jakiś plik *.cs z tego szablonu, zobaczysz że zamiast konkretnej nazwy namespace’a masz coś takiego: $safeprojectname$, np:
using System;
namespace $safeprojectname$
{
internal class MyClass
{
}
}
Wizard sprytnie podmienił nazwy Twoich namespace’ów na parametr $safeprojectname$. Dzięki temu, jeśli będziesz tworzył nowy projekt oparty o ten szablon, znajdzie się on w poprawnym namespace. I tu dochodzimy do tematu parametrów…
Parametry w szablonie
W swoim szablonie możesz mieć różne parametry. Parametry są otulone dolarami (jakby to nie brzmiało ;)), czyli tak: $nazwaParametru$. Wielkość liter MA znaczenie.
Jak się pewnie domyślasz po poprzednim akapicie, jest kilka parametrów predefiniowanych:
Parametr
Opis
clrversion
Aktualna wersja common language runtime (CLR).
ext_*
Prefix, który odnosi się do parametru w nadrzędnym szablonie (o tym później).
guid[1-10]
GUID, który podmienia GUID projektu. Możesz używać 10 unikalnych takich identyfikatorów (np. guid1 – guid10).
itemname
Nazwa aktualnego pliku (tego, w którym znajduje się parametr).
machinename
Nazwa aktualnego komputera
projectname
Nazwa projektu podana przez użytkownika podczas jego tworzenia. Dotyczy jedynie szablonów projektu.
registeredorganization
Wartość z rejestru HKLM\Software\Microsoft\Windows NT\CurrentVersion\RegisteredOrganization wskazująca na zarejestrowaną organizację.
rootnamespace
Główny namespace projektu. Po nim następuje nazwa podfolderu aktualnego elementu oddzielona kropką.
defaultnamespace
Główny namespace aktualnego projektu.
safeitemname
Tak samo jak itemname, jednak tutaj wszystkie niedopuszczalne znaki są zastępowane podkreślnikiem. Np zamiast: „moj projekt” będzie: „moj_projekt”.
safeitemrootname
Tak samo jak itemname.
safeprojectname
Nazwa projektu podana przez użytkownika podczas tworzenia projektu. Z tą różnicą, że wszystkie niedopuszczalne znaki są zastąpione podkreślnikiem. Tylko dla szablonów projektów.
targetframeworkversion
Aktualna wersja .NET Framework.
time
Aktualny czas w formacie ustawionym w ustawieniach systemu Windows.
specifiedsolutionname
Nazwa solucji. Gdy, podczas tworzenia projektu, użytkownik zaznaczy opcję „Create solution Directory”, wartością tego parametru jest nazwa solucji. W innym wypadku ta wartość jest pusta.
userdomain
Aktualna domena użytkownika.
username
Aktualna nazwa użytkownika.
year
Aktualny rok w formacie YYYY.
A o tworzeniu własnych parametrów będzie później w tym artykule.
Jak działa ten mechanizm? Gdy będziesz tworzył projekt oparty na takiem szablonie, wszystkie parametry – wszystkie jego wystąpienia zostaną po prostu zamienione na odpowiednie wartości. Nie ważne, czy będą częścią stringa, czy kodu – WSZYSTKIE wystąpienia będą zastępione.
Szablon z wieloma projektami
Jak już mówiłem, szablon z kilkoma projektami robi się nieco inaczej. Stwórz sobie jakąś przykładową solucję. Ja utworzyłem solucję z dwoma projektami – jednym konsolowym i drugim class library:
Teraz dla każego projektu w tej solucji musisz wykonać eksport. Czyli tak jak wyżej. Z Menu Project wybierz Export template dla każdego projektu z osobna. Różnica będzie taka, że nie będziesz importować poszczególnych szablonów.
Pamiętaj, żeby odznaczyć opcję „Automatically import the template...”, bo w przeciwnym razie Visual Studio automatycznie zaimportuje utworzone templaty z poszczególnych projektów.
Gdy już wszystkie projekty zostały wyeksportowane jako poszczególne szablony, musisz utworzyć szablon nadrzędny, który będzie zawierał pozostałe.
To robisz ręcznie. Przejdź do katalogu, w którym utworzyły Ci się szablony. U mnie to: C:\Users\Admin\Documents\Visual Studio 2022\My Exported Templates\ (Wizard pokazuje Ci to w okienku Output Location, co widać na zrzucie wyżej).
Przejdź do tego katalogu. Zobaczysz w nim pliki zip swoich szablonów. Wypakuj je do ich katalogów:
Wypakowany katalog powinien zawierać bezpośrednio pliki szablonu. Bez żadnych dodatkowych zagnieżdżeń.
Teraz możesz już pozbyć się tych zipów i utworzyć plik XML z rozszerzeniem vstemplate:
Dorzuciłem tutaj też plik icon.ico – to jest ikonka, którą będzie posiadał szablon główny.
OK, to teraz pozostało uzupełnienie pliku vstemplate. Oto przykładowa zawartość:
TemplateData.Name – nazwa Twojego szablonu. Taka będzie widoczna w VisualStudio
TemplateData.Description – opis widoczny w VisualStudio
TemplateData.Icon – ikonka
TemplateData.ProjectType – typ projektu widoczny w VisualStudio. Ja wybrałem CSharp, ale oczywiście może to być dowolny język.
Następnie masz kolekcję szablonów poszczególnych projektów. Dodajesz po prostu te szablony wyeksportowane wcześniej.
ProjectName to będzie domyślna nazwa projektu w VisualStudio, gdy utworzysz go z tego szablonu:
Teraz zawartość tego folderu potraktuj ZIPem:
Na końcu otrzymany plik ZIP skopiuj do katalogu z templateami projektów. U mnie to jest: C:\Users\Admin\Documents\Visual Studio 2022\Templates\ProjectTemplates.
Jeśli jednak koniecznie MUSISZ zrobić w taki sposób szablon z Wizardem, musisz utworzyć projekt o typie Project Template (i kilka innych, ale ten jest podstawowy) i pamiętaj:
Visual Studio albo ma buga, albo zamierzoną funkcję. Nie możesz swojego projektu nazwać ProjectTemplate, ponieważ nie spodoba się to edytorowi.
Druga rzecz jaka może Cię zaskoczyć, to, że musisz używać pełnego frameworka, np. .NET Framework 4.7.2. Tak działa Visual Studio. W tym momencie będziesz tworzyć rozszerzenie dla VisualStudio, które pracuje pod Windowsem. Nie da się w taki sposób stworzyć szablonu (ani rozszerzenia) dla innego systemu. Dlatego, że VisualStudio korzysta z pełnej wersji Frameworka.
Jeśli nie tworzyłeś nigdy wcześniej żadnego rozszerzenia dla VS, dużo rzeczy może wydawać Ci się zawiłymi, części możesz nie zrozumieć. To kolejny powód, dla którego nie opisuję tego zagadnienia – za dużo wiedzy trzeba by było przekazać w jednym artykule. Ale na bank napiszę coś kiedyś o tworzeniu rozszerzeń (zwłaszcza w VS2022 jest to dużo przyjemniejsze niż w VS2015) i wtedy być może wrócę do tematu.
Szablon z Wizardem można zrobić dużo prościej za pomocą nowego podejścia do tworzenia szablonów (szablon dla .Net), o czym napiszę w kolejnym artykule.
To tyle, dzięki za przeczytanie tego artykułu. Jeśli czegoś nie rozumiesz lub znalazłeś błąd, daj znać w komentarzu. I przygotuj się na kolejny z serii o szablonach – tym razem szablony .NET.
Któż z nas nie kocha pisania dokumentacji? 😉 No właśnie. Nikt tego nie chce robić, ale każdy chciałby mieć dokumentację do zewnętrznych systemów. Niestety tworzenie takich materiałów jest po prostu upierdliwe… Ale nie musi. W tym artykule pokażę Ci jak szybko i prosto zrobić bardzo funkcjonalną dokumentację dla własnego API.
Co to Swagger?
Swagger to narzędzie, które magicznie skanuje Twoje API i tworzy stronę, na której ładnie opisuje wszystkie końcówki. Co więcej, umożliwia testowanie takiego API na żywym organizmie. To jest dokumentacja w pełni interaktywna.
Wszystko zrobisz w Visual Studio – nie musisz otwierać żadnego innego edytora. Zaczynamy.
Dodawanie Swaggera do projektu
Swagger jest tak fajnym narzędziem, że Microsoft pozwala na dodanie go już podczas tworzenia samego projektu. W oknie konfiguracji możesz wybrać, czy go używać, czy nie.
To oczywiście najprostsza droga do dodania Swaggera. Ale być może jest tak, że masz projekt, w którym nie zaznaczyłeś tej opcji. Tak też się da.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
Tutaj mała uwaga. Być może pracujesz nad API, które będzie wystawiane zewnętrznie dla klientów. W takim przypadku prawdopodobnie nie powinieneś dodawać Swaggera tylko w środowisku deweloperskim ale produkcyjnie też.
I teraz małe wyjaśnienie:
builder.Services.AddSwaggerGen(); – rejestruje serwisy potrzebne do obsługi Swaggera
app.UseSwagger(); – to podstawowa obsługa
app.UseSwaggerUI(); – dodaje do Twojego API specjalną stronę, na której wszystko jest ładnie opisane, a w dodatku można testować.
To tyle, jeśli chodzi o podstawową konfigurację.
Przykładowy projekt
Swaggera najlepiej pokazać na przykładzie. W związku z tym przygotowałem prostą solucję, składającą się z dwóch projektów. SwaggerDemo to jest nasze API, SwagerDemo.Models to projekt przechowujący modele aplikacji. Specjalnie są zrobione dwa projekty, żeby Ci pokazać coś więcej. Cały gotowy kod możesz sobie sklonować z GitHuba: https://github.com/AdamJachocki/SwaggerDemo
Jeśli nie chcesz korzystać z mojego projektu, po prostu dodaj Swaggera do swojego (tak jak to opisane wyżej).
Możesz teraz uruchomić projekt API. Ważne, żeby API otwierało przeglądarkę. Jeśli Twoje nie otwiera, możesz zmodyfikować plik Properties/launchSettings.json, zmieniając wartość zmiennej launchBrowser na true. Możesz też automatem otworzyć stronę Swaggera, dodając do lauchSettings.json zmienną launchUrl:
Jeśli dodawałeś Swaggera automatycznie (lub zmodyfikowałeś launchSettings.json jak wyżej), prawdopodobnie od razu pokazuje Ci się jego strona. Jeśli nie, doklep w przeglądarce końcówkę swagger. Przykładowo, jeśli adres Twojego API to http://localhost:5001, przejdź na: http://localhost:5001/swagger.
Tak mniej więcej wygląda podstawowa dokumentacja wygenerowana Swaggerem. Osobno widzisz każdy kontroler, w kontrolerze kolekcję endpointów, każdy rodzaj endpointa (POST, GET, DELETE) ma swój kolor. Jeśli rozwiniesz endpoint, zobaczysz dokładnie jakie przyjmuje dane, co zwraca i będziesz mógł go wywołać (przycisk Try it out z prawego, górnego narożnika). Swagger automatycznie rozpoznaje dane wchodzące:
Niemniej jednak, zgodzisz się że to słaba dokumentacja i właściwie niczego nie mówi. Poza tym, że pozwala Ci wysłać żądanie po kliknięciu przycisku Try it out. Ale spokojnie. Zaraz się tym zajmiemy.
Dokumentacja generowana z komentarzy
Każdy endpoint możesz dokładnie opisać za pomocą komentarzy dokumentujących, np:
/// <summary>
/// Pobiera użytkownika po przekazanym id
/// </summary>
/// <param name="id">Id użytkownika</param>
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
User testUser = new User
{
Email = "test@example.com",
Id = 1,
Name = "Test"
};
return Ok(testUser);
}
Tekst jaki wpisałeś w <summary> pojawi się jako opis konkretnego endpointa. Natomiast opisy parametrów <param> pojawią się przy parametrach. Jednak żeby to zadziałało, musisz dokonfigurować projekt API.
Możesz też zrobić to z poziomu ustawień projektu: Build -> Output -> Documentation file:
To ustawienie sprawi, że VisualStudio podczas budowania aplikacji, utworzy specjalny plik XML z metadanymi dokumentacji. Plik będzie nazywał się tak jak projekt, np: SwaggerDemo.xml. I domyślnie tworzy się w katalogu wynikowym.
To ustawienie jednak spowoduje również mały efekt uboczny. Podczas budowania aplikacji otrzymasz warningi CS1591, mówiące o tym, że są publiczne metody, które nie mają komentarzy dokumentujących. My tutaj dokumentujemy tylko metody w kontrolerach, aby Swagger mógł zadziałać. Jeśli nie dokumentujesz wszystkich metod publicznych, możesz ten warning wyłączyć, dodając do pliku projektu:
Swagger odczytuje opisy właśnie z tego pliku XML. Trzeba mu to tylko powiedzieć. Robisz to, podczas konfigurowania Swaggera w kodzie przy konfiguracji serwisów:
builder.Services.AddSwaggerGen(o =>
{
var assemblyName = Assembly.GetExecutingAssembly().GetName().Name + ".xml";
var docFile = Path.Combine(AppContext.BaseDirectory, assemblyName);
o.IncludeXmlComments(docFile);
});
Tutaj nie ma żadnej magii. Kluczową instrukcją jest IncludeXmlComments, gdzie w parametrze podaję pełną ścieżkę do utworzonej automatycznie dokumentacji xml. Czyli pobieram ścieżkę wykonywanego pliku, pobieram nazwę projektu i łączę to.
Teraz dokumentacja Swaggerowa wygląda już tak:
Opisywanie odpowiedzi
Swaggerowi możesz powiedzieć jeszcze, jakie endpoint generuje odpowiedzi i kiedy:
/// <summary>
/// Pobiera użytkownika po id
/// </summary>
/// <param name="id">Id użytkownika</param>
/// <response code="200">Zwraca znalezionego użytkownika</response>
/// <response code="404">Nie znaleziono takiego użytkownika</response>
/// <response code="500">Wewnętrzny błąd serwera</response>
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
User testUser = new User
{
Email = "test@example.com",
Id = 1,
Name = "Test"
};
return Ok(testUser);
}
Teraz strona Swaggera wygląda tak:
Patrząc na taką dokumentację nadal nie wiesz, jakie dane zwróci endpoint, jeśli zapytanie zakończy się sukcesem (kod 200). Możesz oczywiście wywołać tę końcówkę z poziomu Swaggera i otrzymasz wszystkie dane pobrane z API:
Jednak jest pewien sposób…
Opisywanie zwracanego modelu
Możesz pokazać Swaggerowi zwracany model:
[Route("api/[controller]")]
[ApiController]
[Produces("application/json")]
public class UsersController : ControllerBase
{
/// <summary>
/// Pobiera użytkownika po id
/// </summary>
/// <param name="id">Id użytkownika</param>
/// <response code="200">Zwraca znalezionego użytkownika</response>
/// <response code="404">Nie znaleziono takiego użytkownika</response>
/// <response code="500">Wewnętrzny błąd serwera</response>
[HttpGet("{id}")]
[ProducesResponseType(typeof(User), 200)]
public IActionResult GetById(int id)
{
User testUser = new User
{
Email = "test@example.com",
Id = 1,
Name = "Test"
};
return Ok(testUser);
}
}
Tutaj zrobiłem dwie rzeczy. Na poziomie kontrolera powiedziałem, jaką odpowiedź kontroler zwraca (json). Co nie jest wymagane, ale lepiej wygląda w Swaggerze. No i oczywiście klienci Twojego API nie mają żadnych wątpliwości co do rodzaju zwrotki. W innym przypadku Swagger pokaże combobox z możliwością wyboru typu zwrotki.
Ważniejsza jednak rzecz jest na poziomie samego endpointa – atrybut ProducesResponseType. W parametrach pokazuję jaki typ jest zwracany przy jakim kodzie. Różne kody mogą zwracać różne typy modeli. Teraz Swagger wygląda tak:
Jak widzisz Swagger pokazuje teraz szablon zwracanego modelu.
Opisywanie pól modelu
W rzeczywistości modele bywają bardziej skomplikowane niż ten powyżej. A ich pola nie opisują się tak ładnie. Możemy Swaggerowi opisać dokładnie każde pole modelu. Jak? Również za pomocą komentarzy dokumentujących. Tym razem na poziomie konkretnego modelu:
public class User
{
/// <summary>
/// Id użytkownika
/// </summary>
public int Id { get; set; }
/// <summary>
/// Imię i nazwisko użytkownika. Uwaga! Pole może być puste
/// </summary>
public string Name { get; set; }
/// <summary>
/// E-mail użytkownika
/// </summary>
public string Email { get; set; }
/// <summary>
/// Hasło użytkownika. Zawsze puste, gdy pobieramy rekord.
/// </summary>
public string Password { get; set; }
}
Co się stanie, gdy odpalimy teraz Swaggera? Zupełnie nic 🙂
Dlatego też stworzyłem dwa projekty – jeden api, drugi dla modeli.
Przypominam, że Swagger opisy odczytuje z pliku dokumentacji (xml) tworzonego przez Visual Studio. O ile projekt API został ładnie ustawiony, to projekt z modelami nie ma takiej konfiguracji. Musimy ją więc dodać. Do projektu z modelami dodaj znane już elementy:
Teraz jeszcze tylko musisz Swaggerowi powiedzieć, skąd ma ten dokument zaczytać. To też już robiliśmy. Podczas konfiguracji Swaggera trzeba tylko dodać kolejny plik:
builder.Services.AddSwaggerGen(o =>
{
var assemblyName = Assembly.GetExecutingAssembly().GetName().Name + ".xml";
var docFile = Path.Combine(AppContext.BaseDirectory, assemblyName);
o.IncludeXmlComments(docFile);
var modelsAssemblyName = typeof(User).Assembly.GetName().Name + ".xml";
var modelsDocFile = Path.Combine(AppContext.BaseDirectory, modelsAssemblyName);
o.IncludeXmlComments(modelsDocFile);
});
Tutaj, żeby nie wpisywać na sztywno nazwy projektu, posłużyłem się jakąś klasą, która występuje w projekcie z modelami. Traf chciał, że padło na klasę User. Generalnie wybrałem pierwszą lepszą. Chodziło o to, żeby refleksja zwróciła nazwę projektu. Reszta jest taka sama jak wyżej: IncludeXmlComments i wio.
Teraz Swagger wygląda tak:
Pamiętaj, że żeby zobaczyć opisy pól modelu, musisz kliknąć na Schema.
Swagger i wersjonowanie API
Często nasze API są wersjonowane. Swagger niestety nie ogarnia tego domyślnie. Jest kilka sposobów, żeby to zadziałało. Ja Ci pokażę jeden z nich – moim zdaniem najbardziej prawilny.
Jak to działa?
Słowem wstępu, Swagger działa tak, że używa mechanizmu dostarczanego przez Microsoft: EndpointsApiExplorer. Nie musisz tego dodawać ręcznie, to już dodaje Swagger podczas rejestrowania swoich serwisów.
ApiExplorer skanuje Twoje API i zwraca informacje o nim, a Swagger za jego pomocą buduje swoje pliki „map”.
Przy tym podejściu musisz zapewnić, że wersjonujesz API tak jak napisałem tutaj. Głównie chodzi o trzymanie kontrolerów dla różnych wersji w różnych namespace.
Krok 1 – stworzenie konwencji
Na początek musimy utworzyć konwencję, która odpowiednio pogrupuje kontrolery. Stwórz taką klasę:
public class GroupingByNamespaceConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
var controllerNamespace = controller.ControllerType.Namespace;
var apiVersion = controllerNamespace.Split(".").Last().ToLower();
if (!apiVersion.StartsWith("v"))
apiVersion = "v1";
controller.ApiExplorer.GroupName = apiVersion;
}
}
Zadaniem tej klasy jest odpowiednie zgrupowanie kontrolera (dodanie atrybutu GroupName). To grupowanie jest używane tylko przez ApiExplorer, czyli nie ma żadnego znaczenia dla działającego kodu. Zapamiętaj – tylko dla dokumentacji. Teraz trzeba tą konwencję zarejestrować podczas rejestracji serwisów:
Teraz musimy skonfigurować dokumentację dla każdej wersji. Robimy to podczas konfiguracji Swaggera:
builder.Services.AddSwaggerGen(o =>
{
o.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Wersja 1",
Version = "v1"
});
o.SwaggerDoc("v2", new OpenApiInfo
{
Title = "Wersja 2",
Version = "v2"
});
});
Dodałem tutaj dwie wersje. Na koniec trzeba jeszcze je dodać podczas konfiguracji middleware:
Ważne, żeby nazwa przekazana w SwaggerEnpoint (Wersja1, Wersja2) była spójna z tytułem skonfigurowanym w SwaggerDoc.
Krok trzeci – aktualizacja kontrolerów
Na koniec już tylko musisz zaktualizować kontrolery, żeby powiedzieć im, którą wersję API wspierają. Prawdopodobnie będą sytuacje, że pomiędzy pierwszą i drugą wersją API zmieni Ci się tylko część kontrolerów. Wystarczy, że dodasz do nich atrybuty ApiVersion:
[Route("api/[controller]")]
[ApiController]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class ItemsController : ControllerBase
{
}
Ułatwienia
Istnieje NuGet, który ułatwia konfigurowanie wersji w Swaggerze. Jednak komentarz autora (który przytaczam fragmentami niżej) mi daje taką myśl: „Wstrzymaj konie i poczekaj na nową wersję”. Ten NuGet to: Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer, jednak nie daj się zmylić – nie ma za wiele wspólnego aktualnie z Microsoftem:
Projekt rozpoczął się jako eksperyment myślowy jak wziąć pomysły stojące za wersjonowaniem API (zwłaszcza RESTowego) i zaimplementować je w praktyczny sposób (…). Rozwój (…) różnych projektów w Microsoft zajął około dwóch lat, ale w końcu powstał ogólny wzorzec i framework. 6 lat temu (2016 – przyp. tłumacz) przeniosłem to do społeczności open source, żeby rozwijać dalej i szczerze – dla mojego własnego egoistycznego użytku do projektów poza Microsoftem. To, że projekt stał się taki popularny, przerosło moje najśmielsze oczekiwania.
Decyzja, żeby przenieść projekt na Microsoftowy GitHub była głównie podyktowana open source’ową polityką firmy. Pomimo powszechnego przekonania, nie jestem i nigdy nie byłem częścią teamu od AspNetCore (…). Ten projekt nigdy w żaden sposób nie był oficjalnie wspierany. Pomimo, że pojawiło się kilku zewnętrznych kontrybutorów, głównie utrzymywałem go sam. W 2021 (…) zdecydowałem się opuścić Microsoft (…). Próbowałem zachować projekt i przekazać go, jednak pojawiło się wiele wyzwań. Zajęło to kilka miesięcy, ale ostatecznie uznałem, że najlepszym będzie przeniesienie projektu z organizacji Microsoft do .NET Foundation (…).
Pojawiło się kilka nowych problemów, m.in. nazwa, która wskazuje, że projekt jest zarządzany przez Microsoft (…). Chciałem zrobić fork projektu i rozpocząć nowy, jednak mogłoby to wprowadzić zamieszanie w społeczności (…).
Drugi problem to identyfikatory pakietów NuGet. Zasugerowano, że po prostu wyślę zawiadomienie, że identyfikator się zmieni. Jednak po 100 milionach pobrań stwierdziłem, że jest to niedopuszczalne. Zajęło to wiele miesięcy aby wyśledzić odpowiednich interesariuszy NuGet, aby rozpocząć proces, ale identyfikatory pakietów zostały teraz przeniesione do zespołu api-versioning z dotnetfoundation. Jeśli zastanawiasz się, dlaczego nie było żadnych aktualizacji od dłuższego czasu, to właśnie dlatego. Teraz mam trochę więcej kontroli nad pakietem i aktualizacje mogą znów się pojawiać. Jednak są z tym związane limity. Nowe funkcje nie mogą pojawiać się pod szyldem Microsoft(…). Zacząłem nawet prace nad nową wersją, która zaczynałaby się prefixem Asp.Versioning.* (…).
Krótko mówiąc – projekt powinien mieć jakieś aktualizacje do wersji 5.*. Jednak niczego więcej po nim nie można się spodziewać. A jego klon gdzieś kiedyś się pojawi.
To tyle, jeśli chodzi o dokumentowanie API. Jak widzisz, nie musi to być tak nudne, jak się wydaje. A i musisz przyznać, że dla klienta taka interaktywna dokumentacja ma dużo większą wartość niż tabelka w Wordzie. Spróbuj sam i zobacz, jak to jest. Co więcej, Swagger posługuje się standardem OpenAPI 3.0, więc możesz to sobie zaimportować nawet do PostMana! 🙂
Dzięki za przeczytanie artykułu. Jeśli czegoś nie zrozumiałeś lub znalazłeś jakiś błąd, koniecznie podziel się w komentarzu. A jeśli znasz kogoś, komu ten artykuł się zdecydowanie przyda, udostępnij mu go 🙂
Tworząc aplikacje na telefony, czasem pojawia się pytanie „Jak usunąć plik z Androida” albo „Gdzie jest AppData na Androidzie”. Na szczęście wraz z Visual Studio mamy dostępne takie narzędzie jak ADB (Android Debug Bridge), które pozwala na wywoływanie komend shellowych bezpośrednio na telefonie.
Jestem rootem…
Przede wszystkim musisz uruchomić narzędzie adb z opcją root’a. Uruchom adb z menu Visual Studio: Tools -> Android -> Android ADB Command Prompt. Oczom Twoim ukaże się piękne okienko konsolowe. Ale to nie jest zwykła konsola, ta wersja ma dostęp do adb. Teraz, aby uruchomić adb w trybie root’a, wpisz poniższe polecenie:
adb root
W tym momencie możesz dostać kilka odpowiedzi:
nie można tego zrobić ze względu na jakiś błąd, np: „adb: unable to connect for root: no devices/emulators found” – co znaczy, że nie znaleziono żadnego urządzenia – wtedy po prostu uruchom emulator lub podłącz fizyczne urządzenie do kompa
serwer już chodzi jako root („adbd is already running as root”)
zrestartowano serwer w trybie roota (w tej sytuacji możesz nie otrzymać żadnego komunikatu)
Jeśli nie możesz zrootować telefonu
Nie każdą wersję emulatora można zrootować. Te z opcją GooglePlay z jakiegoś powodu nie są rootowalne. Dlatego też, jeśli otrzymałeś taki komunikat, musisz się upewnić, że nie masz w emulatorze opcji Google Play. Z menu Visual Studio wybierz: Tools -> Android -> Android Device Manager
Z tego managera wyedytuj emulator, którego chcesz używać
Wszystko rozbija się o to, żeby telefon nie miał opcji Google Play. Pozostaw to puste:
Gdy odznaczysz tę opcję i klikniesz OK, manager zainstaluje nowe urządzenie bez Google Play Store. Takie urządzenie będziesz już mógł rootować za pomocą komendy adb root tak jak wyżej.
Połączenie z telefonem
Gdy już udało Ci się uruchomić serwer adb w trybie roota, możesz teraz „połączyć się” z jego shellem. Po prostu wpisz polecenie:
adb shell
Jeśli masz kilka urządzeń, będziesz musiał podać nazwę konkretnego. Najpierw pobierz te nazwy poleceniem:
adb devices -l
To polecenie pokaże Ci pełną listę podłączonych urządzeń, np:
List of devices attached emulator-5554 device product:sdk_gphone_x86 model:Android_SDK_built_for_x86 device:generic_x86 transport_id:5
Teraz możesz połączyć się z konkretnym urządzeniem, podając jego nazwę:
adb -s emulator-5554 shell
lub jego transport_id:
adb -t 5 shell
Jeśli wszystko poszło ok, powinieneś zobaczyć taką lub podobną zachętę:
generic_x86:/ #
UWAGA!
Jeśli zamiast hasha (#) widzisz dolar ($), to znaczy, że nie jesteś zalogowany jako root i coś poszło nie tak. Sprawdź, czy wykonałeś wszystkie kroki jeszcze raz.
W tym momencie możesz bawić się shellem na swoim emulatorze.
Jak usunąć plik?
Jeśli znasz chociaż troszeczkę polecenia Linuxa… liznąłeś go kiedykolwiek, to już jest dobrze. Jeśli kojarzysz polecenia takie jak ls, czy cd, to nie powinieneś mieć więcej pytań. Po prostu czuj się jak na Linuchu. Jeśli jednak jesteś zielony w te klocki, to lepiej żebyś ogarnął podstawy pracy z konsolą Linuxa. Kilka podstawowych poleceń w zupełności wystarczy:
ls – skrót od LIST, a więc listuje wszystkie „foldery” będące na tym poziome
cd – tak jak w Windows – Change Directory – przechodzi do wskazanego „folderu”
rm – usuwa podany plik
I to wszystko… Hulaj dusza, piekła nie ma!
Gdzie pliki z mojego programu?
Pliki programu powinieneś umieszczać w odpowiednim katalogu z Environment.SpecialFolder. W taki sposób też prosto możesz poznać dokładną ścieżkę do takiego katalogu:
string targetDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); //tutaj przykład do LocalAppData
Jeśli masz problem lub znalazłeś błąd w artykule, podziel się w komentarzu
Zazwyczaj tego nie robię. Gdy wychodzi nowy Visual, zazwyczaj czekam około pół roku zanim go zainstaluję i zacznę używać. Po prostu chcę uniknąć głupich problemów „wieku dziecięcego”. Tym razem jednak było inaczej. Z jakiegoś powodu nie mogłem doczekać się Visual Studio 2022. Zainstalowałem sobie nawet wersję Preview, ale w sumie nie używałem jej. W końcu wyszło… Poczułem silną wewnętrzną potrzebę przetestowania tego ustrojstwa i jak najszybszego używania go… W tym artykule dzielę się swoimi pierwszymi spostrzeżeniami. Opisuję wersję 17.0.0 Community
Sprzęt
Być może sprzęt, na którym go testuje ma jakieś znaczenie (na pewno ma w pewnych sytuacjach), dlatego postanowiłem przedstawić Wam go
Testowałem też na innym kompie – 2 spore monitory (z czego jeden 4K) i 16 rdzeni (dokładnej konfiguracji nie pamiętam)
Instalacja
Tutaj nie ma nowości. Pobrany instalator musiał najpierw ściągnąć kilka rzeczy, co zajęło mu około 30 sekund. Następnie uruchomił się już znany Visual Studio Installer, w którym wybrałem takie składniki:
ASP.NET Core, ASP.NET
Node.js
Aplikacje mobilne (Xamarin)
Programowanie aplikacji klasycznych .NET
Magazynowanie i przetwarzanie danych
Programowanie rozszerzeń Visual Studio
Niektóre „podskładniki” jak np. środowisko uruchomieniowe .NET 5 miałem już zainstalowane, więc czas instalacji będzie nieco krótszy.
W każdym razie, wszystko zajęło 11,12 GB. A całkowity czas instalacji to około 11 minut. Całkiem nieźle, biorąc pod uwagę, że AKTUALIZACJA VS2019 z wersji 16.11.5 na 16.11.6 zajęła prawie 7 i pół minuty.
Otwieranie solucji
Widziałem jak MS chwalił się szybkością otwierania solucji z ponad 100 projektami. Na pokazie trwało to naprawdę szybko. Żeby niczego nie zaburzyć, postanowiłem pierwsze otwarcie przeprowadzić bez żadnego kodu. Podejrzewam, że podczas pierwszego uruchamiania VS robi więcej rzeczy niż zazwyczaj.
Otwarcie solucji z 36 projektami trwało 50 sekund. Przy czym otwarcie tej samej solucji na VS2019 trwało 45 sekund. ALE! Najwidoczniej VS2022 optymalizuje sobie w jakiś sposób pracę. Drugie otwarcie tej samej solucji w VS2022 (po restarcie środowiska) zajęło już tylko 30 sekund. Więc faktycznie coś w tym jest.
Wygląd
Wygląd różni się nieco od VS2019. Są inne ikonki, inne czcionki… Wszystko to ponoć ma sprawić przyjemniejszą i bardziej skupioną pracę. Myślę, że ocena tego jest dość indywidualna, a ja jeszcze nie spędziłem przy tym środowisku tyle czasu, żeby móc to ocenić.
Co się jednak rzuca w oczy na początek, to czcionka w edytorze kodu. I tu mam mieszane uczucia. Na dużym monitorze 4K wygląda to całkiem nieźle. Natomiast na moim laptopie z ekranem 17″… było mi jakby za ciasno. Dlatego zmieniłem na starą Consolas. Ale to też kwestia dość indywidualna.
Pierwszy problem…
Na jednym komputerze wszystko poszło podejrzanie sprawnie. Jednak na drugim nie mogłem uruchomić aplikacji .NetCore WPF w środowisku. Ciągle dostawałem błąd:
The target process exited without raising a CoreCLR started event.Ensure that the target process is configured to use .NET Core. This may be expected if the target process did not run on .NET Core
Przeszukałem Internety wzdłuż i wszerz i dupa. Restarty, reinstalacje .NetCore, odinstalowywanie starszych wersji… nic nie dawało rady. Wygląda na to, że podczas pierwszej instalacji czegoś nie dopatrzyłem, co było potrzebne dla mojej aplikacji. Gdy doinstalowałem kilka rzeczy później nadal nic nie działało. Musiałem ostatecznie wybrać opcję „Napraw” z VsInstallera. I wtedy wszystko zaczęło magicznie działać.
Z czego wynika ten problem? W pierwszej wersji VS2019 był to faktycznie jakiś problem ze środowiskiem. Tutaj ciężko mi powiedzieć. Być może środowisko nie ogarnęło czegoś w 100% podczas doinstalowywania modułów.
Hot Reload
Podczas prezentacji niesamowicie jarali się mechanizmem HotReload, który ponoć w tej wersji jest przełomowy. I faktycznie – pokazywali Hot Reload w projekcie w C++, co mnie zupełnie zbiło z tropu. Postanowiłem to sprawdzić na jakimś projekcie MFC…
C++ i MFC
No niestety. Wygląda na to, że MFC jest zbyt starą technologią, żeby wspierało HotReload. Niestety można się było tego spodziewać. Więcej nie testuję C++, chociaż na prezentacji faktycznie to działało (oczywiście nie pokazywali tego na MFC, tylko na jakiejś prostej gierce).
WPF
Na pierwszy rzut oka naprawdę bardzo fajnie to działa. Można robić zmiany zarówno w XAML jak i w kodzie C# (!) i HotReload naprawdę daje radę. Co więcej, nie trzeba nawet zapisywać tych zmian! Wszystko dzieje się na bieżąco. Można zmieniać nawet style, a także user controlsy, co kiedyś było upierdliwe i to działa… ale… no właśnie.
Podczas zmian w globalnych stylach w pliku App.xaml coś się pieprzy i zmiany nie są widoczne. Co więcej, cała aplikacja jakby znalazła się w jakimś dziwnym stanie zawieszenia. Chociaż MS sam mówił, że przy niektórych zmianach w kodzie trzeba jednak zrestartować aplikację. Może ma to jakiś związek.
Co więcej, świetne jest okienko Preview (Debug -> Windows -> XAML Live Preview). Pokazuje aktualne okno uruchomionej aplikacji. Świetnie się sprawdza w przypadku jednego monitora, bo nie trzeba przełączać się między Visualem i aplikacją. Okienko Preview jest po prostu zadokowane. Zmiany są widoczne zarówno w tym oknie, jak i w aplikacji. Okienko preview ma też kilka dodatkowych rzeczy w stylu linijek znanych z programów graficznych. Bardzo to może wspomóc ustawianie elementów.
Dodatkowo jest w stanie pokazać w jakim pliku znajduje się jak element:
Ogólne wrażenie: PLUS. Gdyby nie ten problem z plikiem App.xaml, byłbym niesamowicie zachwycony. Ale nadal – czapki z głów. Naprawdę dobrze działający mechanizm.
Xamarin
HotReload w Xamarinie zazwyczaj działało mi „od święta”. Głównie jednak nie działało, co było bardzo frustrujące i zabierało mnóstwo czasu. Dlatego z wielkim polskim przekąsem na twarzy podszedłem do tego tematu…
No cóż… Dupy nie urywa. HotReload w Xamarinie „potrafi” działać w dwóch trybach (od pewnej wersji). Może odświeżać tylko to, co się zmieniło lub całą stronę.
Okienko Preview udało mi się pokazać tylko w trybie „odświeżania samych zmian”. Natomiast w trybie odświeżania całej strony w ogóle nie działało. Ale to nie jest najgorsze. Tryb odświeżania tylko zmian w ogóle nie chciał działać. Udało się za to uruchomić HotReload w trybie odświeżania całej strony. Nie działa to jednak tak super jak w WPF. Tutaj jest jeszcze trochę pracy do zrobienia.
Jednak, podczas moich testów, HotReload w trybie odświeżania całego ekranu działał cały czas.
Ogólne wrażenie: mieszane… bardziej negatywne. Coś tam działa. Ale kiedy przestanie?
ASP.NET MVC
Niestety tutaj nie działa. Ale MS pisze o tym na swoich stronach. Więc tutaj trzeba posługiwać się starymi metodami. Podobnie jak…
BLAZOR!
Technologia, w której ostatnio się podkochuję. Obiecali, k… obiecali! Obiecali, że będzie Hot Reload! No i jest… Podobno. Gdy używa się .NET6. Aplikacja w Blazor, którą gdzieś tam sobie robię na boku jest na .NET5 i ma dla mnie aktualnie dość niski priorytet. W końcu zmigruję ją do .NET6 i wtedy sprawdzę. Póki co… Smutek…
Designer
Tutaj właściwie napiszę tylko o WPF. W Xamarinie standardowo nie ma takiego pojęcia jak designer, w corowych aplikacjach webowych też nie. Designer w WinForms właściwie się nie zmienił, dlatego opisuję to dla WPF.
Designer w WPF zyskał kilka nowych i fajnych elementów. Przede wszystkim można zmienić niektóre właściwości kontrolki, zaznaczając ją, a następnie klikając żaróweczkę, która się pojawia:
Może i pierdoła, ale naprawdę ułatwia życie.
Ale największy życioułatwiacz dla mnie to element „d” w XAML. Na pewno nie raz miałeś taką sytuację, że musiałeś zaprojektować jakiś element znajdujący się na jakiejś liście. I co wtedy się robi? Trzeba stworzyć jakieś fake’owe dane, żeby elementy były widoczne na liście w design time i generalnie potem trzeba pamiętać, żeby powrócić do prawdziwych danych… Nie jest to zbyt eleganckie rozwiązanie.
Tutaj mamy coś takiego jak wersja w design time (stąd „d”). Kod powie więcej niż 1000 słów:
Jak widzisz mamy tutaj właściwości Text i d:Text. FontWeight i d:FontWeight. Te prawdziwe właściwości (Text i FontWeight) działają normalnie – w runtime, czyli gdy aplikacja jest uruchomiona. Jeśli jednak jesteś w design time (aplikacja jest projektowana), działają właściwości z elementem d:. Czyli wszystko, co ma d: zostanie użyte w czasie projektowania aplikacji. Dla mnie jest to niesamowita opcja.
Wyszukiwanie plików
Zawsze niesamowicie denerwowało mnie wyszukiwanie plików w solution explorerze. Działało to bardzo topornie, a czasami pokazywało strasznie długą listę, na której trzeba było znaleźć to, co się chce. Właściwie pozostawało używanie thirdparty pluginów…
W wersji 2022 wreszcie działa to lepiej. Szukanie jest zdecydowanie szybsze i pokazuje wyniki, które bardziej pasują do tego, co chcesz znaleźć.
Podpowiedzi w kodzie – sztuczna inteligencja
Nie mówię tutaj o Intellisense. W tej wersji wprowadzili sztuczną inteligencję, która podpowiada Ci, co powinieneś napisać. Niedługo dojdzie do tego, że będziemy programować wciskając tylko tabulator 😉
Działa to tak, że Microsoft przeanalizował mnóstwo kodów, przede wszystkim na GitHubie i na tej podstawie nauczył sieć neuronową… programowania. To oczywiście zbyt duże słowo. Ale faktycznie – sztuczna inteligencja w jakiś sposób rozpoznaje kontekst w jakim się znajduje i w jakiś szatański sposób potrafi przewidzieć, co chcesz napisać. Testowałem to na swoich kodach. W miejscach, gdzie klasy były raczej unikalne i konteksty raczej też. No nie były to miejsca „typowe”. I ku mojemu wielkiemu zdziwieniu – działa całkiem nieźle. I nawet nie przeszkadza.
To co widzisz na obrazku powyżej to oczywiście dość „typowe” miejsce, ale wierz mi, że mechanizm jest naprawdę ciekawy. Podczas moich testów kilka razy naprawdę mnie zaskoczył.
MAUI
A gdzie MAUI? Cytują klasyka: „W d… bede grał w gre”. Na obietnicach się skończyło. Chłopaki i dziewczyny nie wyrobili się z MAUI na tą wersję, chociaż można już testować PREVIEW. Jest szansa, że w wersji produkcyjnej pojawi się w przeciągu 6 miesięcy.
Super! Dostałeś nowy monitor 4k! Nic tylko szaleć. Odpalasz swój program napisany w WPF i… gówno widzisz. Wszystko jest za małe. Jak to się dzieje? Przecież obiecali, że WPF ogarnia DPI, czy coś tam i nie trzeba już nic robić…
No tak. Niby ogarnia, ale nie ogarnia zmiany DPI przy kilku monitorach. To automatycznie robią aplikacje pisane w UWP. Natomiast w WPF trzeba zrobić mały, prosty myk. Ale spokojnie, nie musisz przepisywać aplikacji, stosować jakiś ViewBoxów, czy skomplikowanych obliczeń. Wszystko zostanie załatwione w pliku manifestu.
Windows 10 od wersji 1703 używa czegoś takiego jak Per-Monitor v2 awarness mode. Po krótce chodzi o to, że potrafi rozpoznać, kiedy okno aplikacji jest przesuwane na monitor z inną rozdzielczością. Teraz musimy poinformować naszą aplikację WPF, że też to potrafi:
Rozwiązanie
1. Utwórz plik manifestu (jeśli używasz domyślnego) lub otwórz jeśli już go masz.
Aby utworzyć plik manifestu:
kliknij prawym klawiszem myszy na projekt
wybierz Add -> New Item
odnajdź plik manifestu (wpisz w okienko do szukania: manifest)
2. Jeśli utworzyłeś nowy plik manifestu, to prawdopodobnie już wszystko masz. Wystarczy znaleźć fragment <application xmlns="urn:schemas-microsoft-com:asm.v3"> i go odkomentować.
3. Powinieneś w pliku manifestu mieć taki fragment:
Po pewnych zmianach w Visual Studio 2019, tworzenie projektów może być nieco niezrozumiałe zwłaszcza dla młodszych programistów. Ten artykuł wyjaśnia po kolei typy projektów w VisualStudio i jest kierowany głównie do początkujących.
Jaki utworzyć projekt
Stworzyłem specjalnie na potrzeby tego artykułu prosty i nieidealny algorytm do wyboru typu projektu. Możesz go pobrać stąd
Co możemy utworzyć
Visual Studio daje nam właściwie nieograniczone możliwości. Pomaga utworzyć projekt na dowolną platformę w konkretnym języku i w wybranej technologii (gdzie można). Tak wygląda okienko tworzenia nowego projektu:
Okienko jest podzielone na dwie główne grupy. Z lewej strony znajduje się lista ostatnich używanych typów projektów. Po prostu możesz kliknąć na to, co już ostatnio tworzyłeś i nie musisz szukać w liście po prawej stronie.
Z prawej strony masz z kolei grupę podzieloną na dwa elementy. Na górze filtr, na dole listę dostępnych typów projektów. One mogą być różne w zależności od tego, jak zainstalowałeś Visual Studio (typy też można dodawać i usuwać z poziomu instalatora: Visual Studio Installer, który znajduje się na Twoim komputerze)
Jeśli teraz zaczniesz wpisywać w filtrze na górze nazwę typu projektu, który chcesz utworzyć, lista automatycznie zacznie się zmieniać. Ja przykładowo wpisałem Console i zobacz, jak wygląda moja lista na obrazku wyżej. Tych projektów jest dużo więcej. Teraz postaram się część z nich omówić:
Add-In
To jest po prostu rozszerzenie (plugin) do Visual Studio. Tak, można pisać własne pluginy. Będę o tym pisał w niedalekiej przyszłości. Tak naprawdę całe Visual Studio składa się z pluginów. Jak widzisz są to projekty VSIX.
VSIX Project
To projekt rozszerzenia VisualStudio z dodanymi już pewnymi plikami i kodami. Takich projektów jest kilka w zależności od języka, w którym piszesz (C#, VisualBasic)
Empty VSIX Project
jest to zupełnie pusty projekt rozszerzenia VisualStudio. Od tego powyżej różni się brakiem kodów i domyślnych plików. Wszystko piszesz od zera. Ma to swoje plusy i minusy.
Empty VSIX Project (Community)
to samo, co wyżej z tą różnicą, że zawiera referencje do biblioteki Community.VisualStudio.Toolkit, co ułatwia pisanie rozszerzeń
VSIX Project w/Command (Community)
ten template zawiera to co VSIXProject, dodatkowo posiada referencje do Community.VisualStudio.Toolkit i dodatkowo posiada napisaną już komendę. Ten suffiks w/Command czytaj jako „with command”
VSIX Project w/Tool Window (Community)
Uuuu, to jest dopiero mocarz. Zawiera wszystko to, co VSIX Project w/Command (Community), ale dodatkowo własne okno narzędziowe (Tool window)
Który projekt wybrać do tworzenia rozszerzenia? To oczywiście zależy. Najbardziej obszernym zdecydowanie jest VSIX Project w/Tool Window (Community) i może się wydawać najlepszym. Tam, gdzie używasz okien i komend może to być szybki start. Oczywiście jeśli wcześniej nie tworzyłeś żadnych rozszerzeń, polecam zaczynać od najmniejszego projektu Empty VSIX Project. Nie przytłoczy Cię duża ilość plików na początek i będziesz mógł rozwijać rozszerzenie w swoim tempie, ucząc się powoli nowych rzeczy.
Console
Czym są aplikacje konsolowe?
Aplikacje konsolowe tworzone są na komputery. Często są to jakieś narzędzia dostępne z wiersza poleceń lub proste programy testowe, gdzie szybko chcemy sprawdzić jakieś funkcjonalności.
Ale aplikacje konsolowe potrafią być często naprawdę dużymi, poważnymi narzędziami. Czasami posiadają nakładki graficzne, dzięki którym łatwiej je obsługiwać. Zdarzają się też proste gry tekstowe lub z grafiką w postaci ASCII Art.
Typy aplikacji konsolowych
Console Application (dostępna w różnych językach: C#, F#, VisualBasic…) – podstawowa aplikacja konsolowa na komputery. Podczas jej tworzenia zostaniesz zapytany o wersję .NetCore, której używać
Domyślnie w tej wersji Visual Studio aplikacje konsolowe są tworzone w .NetCore. W tym miejscu możesz wybrać wersję, której chcesz używać.
Jest to właściwie najmniejszy template. Domyślnie posiada tylko jeden plik Program.cs z małym fragmentem kodu.
Chociaż długo sam nie potrafiłem tego pojąć, to jednak aplikacje konsolowe są tym typem, od którego powinno zaczynać się naukę programowania. Po prostu jest wtedy dużo prościej pojąć pewne rzeczy.
Blank Node.js console application
są dwie wersje zależne od języka. JavaScript lub TypeScript. Jeśli nie wiesz, czym jest TypeScript, to po prostu JavaScript z typami. Sam TypeScript jest kompilowany później do JavaScript. Generalnie to jest najmniejszy template do tworzenia aplikacji w Node.js
Console App (.Net Framework)
to jest aplikacja konsolowa, zupełnie taka jak domyślna Console Application. Też dostępna w kilku językach. Różnica jest taka, że używając tego templatu, będziesz miał do dyspozycji cały .Net Framework. Ale uwaga, ten typ projektu tworzy aplikacje jedynie dla Windowsa.
Standalone code analysis tool
to służy do tworzenia analizatorów kodu. Jakiś czas temu w VisualStudio pojawiły się takie analizatory kodu, które za pomocą Roslyn potrafią podpowiedzieć Ci kilka rzeczy. Np., że powinieneś dany fragment kodu wywołać w wątku głównym. Albo, że możesz jakiś kod napisać czyściej. Dzięki temu VisualStudio potrafi też automatycznie dodać odpowiednie namespacey do pliku cs. Oczywiście nic nie stoi na przeszkodzie, żeby napisać własne analizatory. Do tego służy ten template.
Jak widzisz mamy tutaj właściwie 4 różne typy aplikacji do wyboru – Kosnola dla Windows, konsola niezależna od systemu (.NetCore)*, konsola w Node.js no i analizator kodu.
Pisząc „konsola niezależna od systemu” mam na myśli, że kiedyś tak może być. Na dzień dzisiejszy aplikacje konsolowe i okienkowe są tworzone tylko pod Windows. Ale w przyszłości może się to zmienić. Wtedy dużo prościej będzie przejść z aplikacji pisanej w .NetCore (lub będzie to w standardzie)
Desktop
To jest to, co lubię najbardziej. Stare, dobre aplikacje na komputery :). Tutaj tworzymy głównie aplikacje okienkowe i wszystko co z nimi powiązane (biblioteki, kontrolki, serwisy), ale możemy też znaleźć templaty do testów jednostkowych.
Opiszę pokrótce niektóre typy:
Windows Forms
Windows Forms App – tworzymy aplikację okienkową w technologii WindowsForms. Jedynie dla Windows. Używamy wybranej wersji .NetCore. Dostępne dla C# i VisualBasic
Windows Forms Class Library – biblioteka dll, której możemy używać w aplikacjach Windows Forms App. Jedynie dla Windows.
Windows Forms Control Library – biblioteka, w której możemy tworzyć dodatkowe kontrolki i używać ich w Windows Forms App. Tutaj też piszemy tylko dla Windows i w .NetCore.
Windows Forms * (.NET Framework) – te kilka pozycji jest analogiczne do tych powyżej. Z tą jedynie różnicą, że mamy do dyspozycji pełny .NET Framework
UWAGA! Pamiętaj o tym, że nie możesz mieszać ze sobą technologii .NetCore i .NET Framerowk. To znaczy, że jeśli tworzysz aplikację w .NetCore, to nie będziesz mógł użyć w niej .NET Framework.
WPF (Windows Presentation Foundation)
WPF Application – nowsza technologia niż Windows Forms. Używamy kodu XAML do projektowania okienek. Używamy wybranej wersji .NetCore. Dostępne jedynie dla Windows.
WPF Class Library – analogicznie jak w Windows Forms. Po prostu dllka, której możemy używać w WPF Application. Używamy wybranej wersji .NetCore i piszemy tylko dla Windows
WPF Custom Control Library – analogicznie jak w Windows Forms Control Library. Tworzymy kontrolki dla aplikacji WPF. Używamy wybranej wersji .NetCore i piszemy tylko dla Windows
WPF UserControl Library – tutaj mały haczyk. Możemy tworzyć „kontrolki” dziedziczące po UserControl i używać ich w aplikacji WPF. Nie ma to odpowiednika w WindowsForms, ponieważ WPF wymaga nieco innego podejścia. Używamy wybranej wersji .NetCore i piszemy w Windows.
WPF Application (.NET Framework) – to samo, co wyżej z tą różnicą, że używasz pełnej wersji .NET Framerowk
UWAGA! Pamiętaj o tym, że nie możesz mieszać ze sobą technologii .NetCore i .NET Framerowk. To znaczy, że jeśli tworzysz aplikację w .NetCore, to nie będziesz mógł użyć w niej .NET Framework.
Universal Windows
To są aplikacje, które mogą działać na wszystkich platformach z Windowsem. Na komputerach, telefonach (z Windowsem), XBox… Generalnie wszędzie tam, gdzie jest zainstalowany Windows. Co ciekawe, sam możesz tworzyć swoje urządzenia z odpowiednim chipem i tworzyć aplikacje, które będą na nim pracowały.
Te aplikacje też wykorzystują XAML do tworzenia okienek, ale robi się je zupełnie inaczej niż aplikacje w WPF. Mają zupełnie inny cykl życia i inne kontrolki.
Windows Service
To są usługi, które pracują w systemie. Po prostu takie aplikacje bez okienek, które pracują cały czas w tle.
Mobile
W tej kategorii znajdziemy templaty do tworzenia aplikacji mobilnych. Głównie w technologii Xamarin. Nie będę się tu za bardzo wysilał, bo też nie ma nad czym. Opiszę tylko w kilku słowach, czym jest Xamarin.
Xamarin
Wszystkie aplikacje oznaczone jako Xamarin, są pisane w technologii Xamarin. No tak, a masło jest z masła. Generalnie Xamarin to taka technologia, która próbuje połączyć ze sobą różne platformy. Android, iOS, Universal Windows i inne. Celem, jaki temu przyświeca jest to, żeby jak największa część kodu była współdzielona pomiędzy te wszystkie aplikacje. Zarówno część wizualna (Xamarin.Forms) jak i cała logika.
Mimo, że technologia ma już kilka lat, to jednak cały czas (maj 2021) są z nią jakieś problemy i czasami pewne rzeczy trzeba napisać inaczej niż by się chciało. Ale cały czas jest rozwijana i ulepszana. I faktycznie z roku na rok wygląda to coraz lepiej.
Co do reszty templatów (np. Photo Editing Extension) nie będę się rozpisywał, bo nie miałem z nimi do czynienia. Można się domyślić, że jeśli chcesz napisać aplikację na telewizor, użyjesz odpowiedniego templatu z oznaczeniem tvOS (chyba, że piszesz na Androida). Jeśli interesuje Cię coś konkretnego z tej listy poza Xamarin, odsyłam do google 🙂
Other
U siebie mam tylko jeden typ – Blank Solution. I mogłoby paść pytanie – po co komu pusta solucja z samym tylko plikiem sln? Trochę jest to przeszłość, a czasami tak po prostu zaczyna się większe projekty. Pozwala Ci to od początku panować całkowicie nad strukturą katalogów i dodatkowych plików, których chcesz używać.
Web
No i doszliśmy do momentu, w którym mamy aplikacje typowo Webowe. Czyli to, co dzisiaj zdecydowanie jest na topie. Visual Studio umożliwia nam tu utworzenie kilku typów:
ASP.NET Core Empty
To jest pusta aplikacja webowa. Możesz z niej zrobić WebAPI, stronę internetową lub co tylko dusza zapragnie w webie. Podczas tworzenia zostaniesz zapytany o pewne ustawienia:
TargetFramework – podajesz tutaj, jakiego frameworka chcesz używać. Zazwyczaj wybierzesz .NetCore 3.1 lub .NET 5
Configure for HTTPS – dzisiaj świat na wysokim miejscu stawia bezpieczeństwo. Również w sieci. Jeśli zaznaczysz tę opcję, projekt będzie skonfigurowany do pracy z protokołem HTTPS. Podczas pierwszego debugowania takiego projektu, Visual Studio utworzy i zainstaluje dla Ciebie specjalny certyfikat (Self Signed Certificate), a przeglądarki podczas pierwszego uruchomienia ostrzegą Cię, że ten certyfikat jest niebezpieczny i nie pochodzi z zaufanego źródła. Nie jest to w tym momencie istotne. Ważne, że gdy opublikujesz taką aplikację w sieci, musisz mieć na serwerze poprawny certyfikat. Dzisiaj można go otrzymać za darmo od Let’s Encrypt. Serwer, na którym mam swoje aplikacje – HostedWindows umożliwia utworzenie takiego certyfikatu i automatyczne przedłużenie go, za pomocą jednego kliknięcia.
Enable Docker – jeśli nie wiesz, czym jest docker, to moje wyjaśnienie nic Ci nie powie. Jeśli wiesz, to wiesz do czego służy ta opcja 🙂
A czysta aplikacja wygląda tak:
ASP.NET Core WebAPI
To jest template, który wykorzystasz konkretnie do utworzenia restowego WebAPI. Jeśli nie wiesz, czym jest restowe WebAPI, to bardzo krótko mówiąc…
Wyobraź sobie, że masz aplikację, w której chcesz wyświetlić wszystkich swoich znajomych z Facebooka. Facebook wystawia takie WebAPI (nie jest to restowe, ale dla przykładu załóżmy, że jest) i teraz możesz się z nim skomunikować i zapytać go o listę swoich znajomych, wywołując taki adres: https://api.facebook.com/friends
Ten adres jest tzw. „endpointem”. Podany przeze mnie endpoint nie istnieje w rzeczywistości, został wymyślony na potrzeby artykułu. Ale tak to mniej więcej działa – aplikacja wywołuje odpowiedni adres (endpoint), a serwer, jeśli Twoje uprawnienia na to pozwolą, odpowiada w jakiś ustalony sposób. Najczęściej odpowiedź jest w formie JSON, rzadziej XML.
Podczas tworzenia projektu tego typu, zostaniesz poproszony o dodatkowe informacje:
Większość jest analogicznie jak wyżej z jedną małą różnicą.
Authentication Type – w projekcie WebAPI możesz i powinieneś w jakiś sposób autentykować i autoryzować swoich użytkowników. Ten artykuł zdecydowanie nie jest miejscem na wyjaśnienia co, jak i dlaczego, bo zagadnienie jest dość obszerne. Wkrótce o tym napiszę. Jeśli nie wiesz o co chodzi, to zostaw na None.
Projekt WebAPI nieco różni się od pustego:
Jak widzisz doszedł folder Controllers, zawierający kontrolery i kilka innych plików. Kod też jest inny. Zatem, jeśli chcesz stworzyć WebAPI, to zdecydowanie zacznij od tego projektu. Własne proste WebAPI dzięki temu można stworzyć w 10 minut (co dzisiaj udowodniłem komuś :)).
ASP.NET Core Web App
To jest typowa strona internetowa. Frontend tworzy się w RazorPages. A całość po utworzeniu wygląda tak:
ASP.NET Core App (Model-View-Controller)
To jest już aplikacja webowa, używająca wzorca MVC. Front tworzy się w RazorViews. Generalnie jest to bardzo podobne do RazorPages z pewnymi niuansami. Same dwa templaty (ten i poprzedni) są do siebie bardzo podobne i czasem jest ciężko stwierdzić, który będzie lepszy.
Generalnie zasada jest taka, że do prostych stron używamy raczej Razor Pages (ASP.NET Core App tego pierwszego), natomiast do bardziej skomplikowanych używamy MVC. Dodatkowo ten projekt może zawierać WebAPI. Ale osobiście radziłbym nie mieszać.
Utworzony pusty projekt wygląda tak:
ASP.NET Web Application (.NET Framework)
To jest stary typ aplikacji webowych, który został zastąpiony .NET Core. Niemniej jednak mnóstwo aplikacji jest w tym utrzymywanych i rozwijanych. Odradzam używanie tego templatu do nowych programów.
No to tyle jeśli chodzi o podstawowe typy projektów. Jest ich jeszcze sporo, dodatkowe przychodzą z innymi pluginami, ale ich nazwy raczej same się opisują.
Jeśli znalazłeś w tekście błąd lub masz problem, podziel się w komentarzu
Obsługujemy pliki cookies. Jeśli uważasz, że to jest ok, po prostu kliknij "Akceptuj wszystko". Możesz też wybrać, jakie chcesz ciasteczka, klikając "Ustawienia".
Przeczytaj naszą politykę cookie