Własny szablon dla dotnet new

Własny szablon dla dotnet new

Wstęp

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:

{
  "$schema": "https://json.schemastore.org/template.json"
}

OK, teraz zupełnie podstawowa zawartość tego pliku może wyglądać tak:

{
  "$schema": "https://json.schemastore.org/template.json",
  "author": "Adam Jachocki",
  "classifications": [ "WebApp", "ApiClient" ],
  "identity": "Jachocki.WebAppWithApiTemplate",
  "name": "Web application with API Client",
  "shortName": "waapi",
  "tags": {
    "type": "project",
    "language": "c#"
  }
}

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:

{
  "$schema": "https://json.schemastore.org/template.json",
  "author": "Adam Jachocki",
  "classifications": [ "WebApp", "ApiClient" ],
  "identity": "Jachocki.WebAppWithApiTemplate",
  "name": "Web application with API Client",
  "shortName": "waapi",
  "tags": {
    "type": "project",
    "language": "c#"
  },
  "sourceName": "WebAppWithApiTemplate"
}

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ć?

  1. Użytkownik ma mieć opcję do podania nazwy klienta – to już nam załatwił parametr varApiClientName.
  2. Użytkownik ma mieć możliwość wyłączenia lub włączenia rejestracji serwisów – czyli domyślnej konfiguracji klienta.
  3. 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.

Teraz mój plik appsettings.json wygląda tak:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "varApiClientName": {
    "BaseAddress": "varApiProdBaseAddress"
  } 
}

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:

"varApiClientName": {
  "BaseAddress": "varApiDevBaseAddress"
}

Wycinanie fragmentów kodu

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:

#if ConfigureApiClient
using WebAppWithApiTemplate.ApiClient;
#endif

//...

			builder.Services.AddRazorPages();
#if ConfigureApiClient
			builder.Services.AddvarApiClientNameIntegration(builder.Configuration);
#endif

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.jsonsources – 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):

"sources": [
  {
    "modifiers": [
      {
        "condition": "(!ConfigureApiClient)",
        "exclude": "ApiClient/ServiceCollectionExtensions.cs"
      }
    ]
  }
],

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:

if(!ConfigureApiClient)
{
    exclude("ApiClient/ServiceCollectionExtensions.cs");
}

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:

#if ConfigureApiClient
			builder.Services.AddvarApiClientNameIntegration(builder.Configuration);
#endif

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:

  //#if ConfigureApiClient
  "varApiClientName": {
    "BaseAddress": "varApiProdBaseAddress"
  }
  //#endif

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.

"JsonLibrary": {
  "type": "parameter",
  "datatype": "choice",
  "choices": [
    {
      "choice": "Default",
      "description": "Używaj domyślnej biblioteki do obsługi JSON",
      "displayName": "Domyślny System.Text.Json"
    },
    {
      "choice": "Newtonsoft",
      "description": "Używaj starego, dobrego Newtonsoft",
      "displayName": "Newtonsoft.Json"
    }
  ],
  "defaultValue": "Default"
}

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:

<!--#if (JsonLibrary == 'Newtonsoft')-->
<ItemGroup>
  <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
<!--#endif -->

Musisz tutaj zwrócić uwagę na dwie rzeczy:

  1. 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.
  2. 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:

"Framework": {
  "type": "parameter",
  "description": "The target framework for the project.",
  "datatype": "choice",
  "choices": [
    {
      "choice": "net6.0",
      "description": "Target net6.0"
    },
    {
      "choice": "netcoreapp3.1",
      "description": ".NetCore 3.1"
    },
    {
      "choice": "net7.0",
      "description": ".NET 7.0"
    }
  ],
  "replaces": "net6.0",
  "defaultValue": "net6.0"
}

Zwróć uwagę tutaj na dwie rzeczy:

  • 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.
Zobacz, jak sprytnie Visual Studio rozkminił wersje

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:

"IsLTS": {
  "type": "computed",
  "value": "(Framework == net6.0 || Framework == netcoreapp3.1)"
}

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:

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
  <metadata>
    <id>Jachocki.ApiClient</id>
    <version>1.0.0</version>
    <description>Przykładowy projekt szablonu</description>
    <authors>Adam Jachocki</authors>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <license type="expression">MIT</license>
    <tags>dotnet templates apiclient webapi</tags>
	<packageTypes>
      <packageType name="Template" />
    </packageTypes>
  </metadata>
  <files>
    <file src="..\**\*.*" exclude="..\**\bin\**\*.*;..\**\obj\**\*.*;" />
  </files>
</package>

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:

{
  "$schema": "https://json.schemastore.org/dotnetcli.host.json",
  "symbolInfo": {
    "ApiClientName": {
      "longName": "ApiClientName",
      "shortName": "cn"
    }
  }
}

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.

Zacznijmy od zupełnej podstawy:

{
  "$schema": "https://json.schemastore.org/ide.host.json",
  "icon": "icon.png"
}

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:

{
  "$schema": "https://json.schemastore.org/ide.host.json",
  "icon": "icon.png",
  "supportsDocker": true,
  "defaultSymbolVisibility": true
}

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:

"SpecialCustomOperations": {
  "**/*.cs": {
    "flagPrefix": "/*add-usings",
    "operations": [
      {
        "type": "replacement",
        "configuration": {
          "original": "/*add-usings",
          "replacement": ""
        }
      },
      {
        "type": "replacement",
        "configuration": {
          "original": "add-usings*/",
          "replacement": ""
        }
      }
    ]
  }
}

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.

W związku z tym, że ten artykuł nie jest kompletnym przewodnikiem, masz tutaj stronę z oficjalnej dokumentacji z opisem tych operacji: https://github.com/dotnet/templating/wiki/Reference-for-template.json#global-custom-operations-and-special-custom-operations

Porady

W tym momencie kończę już ten nieco przydługi artykuł. Dam Ci jeszcze kilka porad na koniec.

  1. Przede wszystkim zainstaluj sobie narzędzie do analizy szablonów:
dotnet tool install --global sayedha.template.command

Następnie możesz przeanalizować swój szablon pod kątem problemów:

templates analyze -f <path-to-folder>

Gdzie <path-to-folder> to ścieżka do katalogu z szablonem (katalogu, w którym jest .template.config)

  1. Dodawaj parametr Framework do template.json.
  2. Jeśli masz jakiś problem ze swoim szablonem – Visual Studio widzi starą wersję albo nie widzi go wcale:
    • odinstaluj szablon
    • usuń zawartość katalogu C:\Users\<nazwa użytkownika>\.templateengine\
    • zainstaluj szablon na nowo
    • jeśli to nie pomaga, czasem pomaga restart Visuala, a czasem restart komputera
  3. Jeśli chcesz wejść mocniej w temat, koniecznie odwiedź:

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 🙂

Podziel się artykułem na:
Tworzenie szablonu projektu w Visual Studio

Tworzenie szablonu projektu w Visual Studio

Wstęp

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:

<VSTemplate Version="3.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Project">
  <TemplateData>
    <Name>ConsoleWithNetCore</Name>
    <Description>Creates new console application with all .NetCore functionality like appSettings, dependency injection and so on.</Description>
    <ProjectType>CSharp</ProjectType>
    <ProjectSubType>
    </ProjectSubType>
    <SortOrder>1000</SortOrder>
    <CreateNewFolder>true</CreateNewFolder>
    <DefaultName>ConsoleWithNetCore</DefaultName>
    <ProvideDefaultName>true</ProvideDefaultName>
    <LocationField>Enabled</LocationField>
    <EnableLocationBrowseButton>true</EnableLocationBrowseButton>
    <CreateInPlace>true</CreateInPlace>
    <Icon>__TemplateIcon.ico</Icon>
  </TemplateData>
  <TemplateContent>
    <Project TargetFileName="ConsoleWithNetCoreTemplate.csproj" File="ConsoleWithNetCoreTemplate.csproj" ReplaceParameters="true">
      <Folder Name="Properties" TargetFolderName="Properties">
        <ProjectItem ReplaceParameters="true" TargetFileName="launchSettings.json">launchSettings.json</ProjectItem>
      </Folder>
      <ProjectItem ReplaceParameters="true" TargetFileName="Application.cs">Application.cs</ProjectItem>
      <ProjectItem ReplaceParameters="true" TargetFileName="appSettings.Development.json">appSettings.Development.json</ProjectItem>
      <ProjectItem ReplaceParameters="true" TargetFileName="appSettings.json">appSettings.json</ProjectItem>
      <ProjectItem ReplaceParameters="true" TargetFileName="Program.cs">Program.cs</ProjectItem>
      <ProjectItem ReplaceParameters="true" TargetFileName="Startup.cs">Startup.cs</ProjectItem>
    </Project>
  </TemplateContent>
</VSTemplate>

Zawartość tego pliku właściwie opisuje się sama.

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:

ParametrOpis
clrversionAktualna 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).
itemnameNazwa aktualnego pliku (tego, w którym znajduje się parametr).
machinenameNazwa aktualnego komputera
projectnameNazwa projektu podana przez użytkownika podczas jego tworzenia. Dotyczy jedynie szablonów projektu.
registeredorganizationWartość z rejestru HKLM\Software\Microsoft\Windows NT\CurrentVersion\RegisteredOrganization wskazująca na zarejestrowaną organizację.
rootnamespaceGłówny namespace projektu. Po nim następuje nazwa podfolderu aktualnego elementu oddzielona kropką.
defaultnamespaceGłówny namespace aktualnego projektu.
safeitemnameTak samo jak itemname, jednak tutaj wszystkie niedopuszczalne znaki są zastępowane podkreślnikiem. Np zamiast: „moj projekt” będzie: „moj_projekt”.
safeitemrootnameTak samo jak itemname.
safeprojectnameNazwa 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.
targetframeworkversionAktualna wersja .NET Framework.
timeAktualny czas w formacie ustawionym w ustawieniach systemu Windows.
specifiedsolutionnameNazwa 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.
userdomainAktualna domena użytkownika.
usernameAktualna nazwa użytkownika.
yearAktualny 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ść:

<VSTemplate Version="2.0.0" Type="ProjectGroup"
    xmlns="http://schemas.microsoft.com/developer/vstemplate/2005">
    <TemplateData>
        <Name>Multi Project Template</Name>
        <Description>Przykład template'u z wieloma projektami</Description>
        <Icon>icon.ico</Icon>
        <ProjectType>CSharp</ProjectType>
    </TemplateData>
    <TemplateContent>
        <ProjectCollection>
            <ProjectTemplateLink ProjectName="Konsolka">
                MultiTemplateConsole\MyTemplate.vstemplate
            </ProjectTemplateLink>
            <ProjectTemplateLink ProjectName="Klaska">
                MultiTemplateClass\MyTemplate.vstemplate
            </ProjectTemplateLink>
        </ProjectCollection>
    </TemplateContent>
</VSTemplate>

Dane w pliku dość ładnie się same opisują.

  • 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:

Nowy projekt utworzony na podstawie 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.

Tworzenie szablonu z Wizardem

Tworzenie szablonu z Wizardem w ten sposób jest trudne. Nie opisuję tego specjalnie. Nigdy nie tworzyłem szablonu w taki sposób i nie potrzebuję tego robić, dlatego też nie opisuję tutaj tego. I tak byłaby to w większości zrzynka z dokumentacji Microsoftu: https://learn.microsoft.com/en-us/visualstudio/extensibility/how-to-use-wizards-with-project-templates?view=vs-2022

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.

Jeśli jednak będzie potrzeba, żeby napisać artykuł o szablonach z Wizardem dla Visual Studio, zrobię to jeśli będę miał wolniejszą chwilę – dajcie znać w komentarzu, czy chcecie coś takiego. Ale mniemam, że to jest na tyle zaawansowane zagadnienie, że ten kto naprawdę potrzebuje, to da sobie radę z załączoną dokumentacją Microsoftu: https://learn.microsoft.com/en-us/visualstudio/extensibility/how-to-use-wizards-with-project-templates?view=vs-2022


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.

Obrazek artykułu: Obraz autorstwa vectorjuice na Freepik

Podziel się artykułem na:
Dokumentowanie własnego API automatem – co to Swagger?

Dokumentowanie własnego API automatem – co to Swagger?

Wstęp

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.

Dodawanie Swaggera ręcznie

Pobierz NuGet:

Install-Package Swashbuckle.AspNetCore.Swagger

Teraz musisz skonfigurować Swaggera.

Dodaj go przy rejestracji serwisów:

builder.Services.AddControllers();
builder.Services.AddSwaggerGen();

A podczas konfiguracji pipeline dodaj:

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:

"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7090;http://localhost:5090",
"environmentVariables": {
  "ASPNETCORE_ENVIRONMENT": "Development"

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.

Konfiguracja projektu

W pliku projektu dodaj:

<GenerateDocumentationFile>True</GenerateDocumentationFile>

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:

<NoWarn>$(NoWarn);1591</NoWarn>

Mój plik projektu API wygląda teraz tak:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>disable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <GenerateDocumentationFile>True</GenerateDocumentationFile>
	<NoWarn>$(NoWarn);1591</NoWarn>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\SwaggerDemo.Models\SwaggerDemo.Models.csproj" />
  </ItemGroup>

</Project>

Konfiguracja Swaggera

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:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>disable</Nullable>
	<GenerateDocumentationFile>True</GenerateDocumentationFile>
	<NoWarn>$(NoWarn);1591</NoWarn>
  </PropertyGroup>

</Project>

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:

builder.Services.AddControllers(o =>
{
    o.Conventions.Add(new GroupingByNamespaceConvention());
});

Krok 2 – konfiguracja dokumentacji Swaggera

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:

app.UseSwagger();
app.UseSwaggerUI(o =>
{
    o.SwaggerEndpoint("/swagger/v1/swagger.json", "Wersja 1");
    o.SwaggerEndpoint("/swagger/v2/swagger.json", "Wersja 2");
});

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 🙂

Podziel się artykułem na:
Rozmowa z telefonem przez terminal

Rozmowa z telefonem przez terminal

Wstęp

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ć

Android Device Manager

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

Podziel się artykułem na:
Visual Studio 2022 – pierwsze spojrzenie

Visual Studio 2022 – pierwsze spojrzenie

Wstęp

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

  • Sprzęt: HP Pro Book 450 G3
  • System operacyjny: Windows 10 19042
  • Procesor: Intel Core i3-6100U 2.30 GHz, 2 rdzenie, 4 logiczne procesory
  • RAM: 16 GB
  • Ekran 17″

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:

Okienko XAML Live Preview wraz z fragmentem XAML

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:

<TextBlock Text="{Binding Name}" 
           d:Text="Nazwa elementu"
           FontWeight="{Binding Modified, Converter={StaticResource BoolToFontWeight}}"
           d:FontWeight="Bold"/>

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.


To na razie tyle z mojego pierwszego spojrzenia. Mimo to, że VS2022 jest jeszcze gorący jak bułki z piekarni, to jednak warto go zainstalować i używać. Jestem naprawdę pozytywnie zaskoczony. Wersję Community możesz pobrać stąd: https://visualstudio.microsoft.com/pl/thank-you-downloading-visual-studio/?sku=Community&rel=17

A Ty zainstalowałeś już VS2022? Jakie są Twoje spostrzeżenia? Podziel się w komentarzu.

Podziel się artykułem na:
Gdy okienko jest za małe, czyli WPF i problem z monitorem 4K

Gdy okienko jest za małe, czyli WPF i problem z monitorem 4K

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:

<application xmlns="urn:schemas-microsoft-com:asm.v3">
  <windowsSettings>
    <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
    <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
  </windowsSettings>
</application>

I tyle. Upewnij się, że Twoja aplikacja używa tego pliku. Aby to zrobić:

  • kliknij prawym klawiszem myszy na projekt
  • wejdź w ustawienia (Properties)
  • na karcie Application upewnij sie, że w ComboBox Manifest masz podany swój plik manifestu.

Uruchom apkę… I to tyle.

Podziel się artykułem na:
Różne typy projektów w VisualStudio 2019

Różne typy projektów w VisualStudio 2019

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

Algorytm wyboru odpowiedniego typu projektu

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 tworzenia nowego projektu w Visual Studio
Tworzenie 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)

WIdok projektu Add-In

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.

Widok pustego projektu Add-In

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”

Widok projektu Add-In Community

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)

Widok projektu Add-In z okienkiem

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.

Konsola

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ć
Wybór framework

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.

Widok projektu konsolowego

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:

Wybór framework
  • 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:

Widok projektu webowego

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:

Wybór framework

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:

Widok projektu WebAPI

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:

Widok projektu RazorPages

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:

Widok projektu Razor MVC

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

Podziel się artykułem na: