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:
Szablon potomny w WordPress

Szablon potomny w WordPress

Hej, w tym artykule pokażę Ci, czym jest szablon potomny w WordPress (child theme), jak i po co go stosować. Rozsiądź się wygodnie i zaczynamy.

Po co szablon potomny?

Prędzej, czy później dojdzie do tego, że będziesz chciał zrobić jakąś zmianę w szablonie – czy to w pliku php, czy css, a może jeszcze gdzieś indziej. To może być mała, ale kluczowa zmiana. Jeśli nie zastosowałbyś szablonu potomnego, to mogłoby się okazać, że po aktualizacji szablonu, który używasz, straciłeś swoje zmiany. No i klops, no i cześć.

Dlatego istotne jest, aby stosować szablony potomne. Dzięki nim, raczej nigdy nie powinieneś stracić swoich zmian (pomijając jakieś duże lub źle zrobione aktualizacje).

Czym jest szablon potomny?

W WordPress są dwa rodzaje szablonów – szablon rodzic (parent theme) i szablon potomny (child theme). Instalując szablon na swoim WordPressie, instalujesz szablon typu rodzic – czyli główny szablon. On może być całkowicie zmieniony podczas aktualizacji.

Szablon potomny to szablon, który tworzysz niejako na podstawie szablonu rodzica. I to właśnie szablonów potomnych powinieneś używać. NIGDY nie używaj szablonu, który instalujesz, za każdym razem utwórz szablon potomny i ten właśnie używaj.

Jak stworzyć szablon potomny?

Na szczęście jest to niesamowicie proste.

  1. Zainstaluj szablon, który chcesz używać, ale nie wybieraj go w ustawieniach WordPress. U mnie to Divi, który szczególnie polecam.
  2. Teraz musisz uruchomić jakąś przeglądarkę plików. Może to być Windows Explorer (jeśli Twoja strona istnieje tylko lokalnie), Total Commander lub jakakolwiek inna przeglądarka (klient FTP)
  3. W katalogu głównym bloga masz katalog wp-content. Przejdź do niego
  4. W nim masz katalog themes. Przejdź tam.
  5. W katalogu themes masz katalogi zainstalowanych szablonów. Utwórz tutaj nowy katalog z nazwą istniejącego szablonu i dopiskiem -child. Tzn. jeśli np. chcesz używać szablonu TwentyTwenty, utwórz katalog TwentyTwenty-child (oczywiście katalog TwentyTwenty musi tutaj istnieć – ponieważ zainstalowałeś ten szablon)
  6. W nowo utworzonym katalogu (z sufiksem -child) utwórz plik style.css. To jest główna część Twojego szablonu potomnego. Do tego pliku dodaj taką zawartość:
/*
 Theme Name:     Nazwa szablonu (np. TwentyTwenty Child)
 Theme URI:      strona szablonu (np. http://twenty-twenty.com)
 Description:    Opis Twojego szablonu (np: Szablon potomny TwentyTwenty)
 Author:         Ja
 Author URI:     strona autora
 Template:       TwentyTwenty (wpisz tutaj nazwę szablonu rodzica)
 Version:        1.0.0
*/ 

Mimo, że powyższy kod jest w komentarzu, to jednak jest on istotny. Krótkie wyjaśnienie:

  • Theme Name – nazwa szablonu, który będzie wyświetlony w menu WordPressa. Nazwa musi być unikalna. Ta wartość jest wymagana
  • Theme URI – strona szablonu, możesz pominąć
  • Description – opis szablonu, możesz pominąć
  • Author – autor szablonu – możesz pominąć
  • Author URI – strona autora szablonu – możesz pominąć
  • Template – szablon rodzic dla tego szablonu. Ta wartość jest wymagana
  • Version – wersja, możesz pominąć.

Jest jeszcze kilka samoopisujących się elementów, które możesz tutaj zawrzeć, ale raczej nie będziesz ich stosował (License – nazwa licencji (np. GNU); License URI – strona z licencją; Tags – tagi szablonu; Text Domain – szczerze, nie mam pojęcia co to. Jeśli wiesz, podziel się w komentarzu)

Kolejkowanie styli szablonów

To właściwie tyle. Utworzyłeś szablon potomny. W pliku style.css możesz dalej robić modyfikacje swojego szablonu. Jest jednak jeszcze jedna rzecz, którą powinieneś zrobić. Powinieneś skolejkować style.css szablonu rodzica i potomnego. Kiedyś robiło się to inaczej (dyrektywa @import w pliku style.css). Ale dzisiaj zalecany jest sposób z wykorzystaniem php.

W świecie idealnym szablon rodzica powinien zaczytać zarówno swoje style, jak i style szablonu potomnego. Jednak nie zawsze to się dzieje. Dlatego powinieneś zrobić to ręcznie:

  1. Utwórz w katalogu szablonu potomnego jeszcze jeden plik: functions.php
  2. Wprowadź mu taką zawartość:
<?php

function dc_enqueue_styles() {
	wp_enqueue_style( 'parent-style', get_template_directory_uri() . '/style.css' );
	wp_enqueue_style( 'child-style', get_stylesheet_directory_uri() . '/style.css', array( 'parent-style' ) );
}
add_action( 'wp_enqueue_scripts', 'dc_enqueue_styles' );

Ten kod po prostu rejestruje i kolejkuje arkusze styli. Teraz jest ważne kilka rzeczy:

  • szablon potomny jest wczytywany przed szablonem rodzicem
  • jeśli nie podasz numeru wersji w pliku style.css, odwiedzający zobaczą to, co zostało zapisane w cache, a nie aktualną wersję. Dlatego po każdej zmianie zmień też numer wersji
  • funkcje get_stylesheet* szukają najpierw elementów w szablonach potomnych

Teraz już możesz aktywować ten styl potomny w opcjach wyglądu WordPressa.

Jeśli znalazłeś błąd w artykule, masz jakieś pytania lub chcesz coś dodać, podziel się w komentarzu

Podziel się artykułem na: