Już kilka tygodni typu pisałem o ASP.NET MVC 5 Scaffolding. Jak wspomniałem, domyślny szablon nadaje się do prototypów albo tymczasowych aplikacji. W praktyce będziemy chcieli wykorzystać IoC i własne usługi, a nie bezpośrednio operować na bazie danych. Za pomocą własnego szablonu, można wszystko dostosować do konkretnego projektu i infrastruktury.
Najpierw ściągamy pakiet szablonów VS z http://sidewaffle.com/. Oprócz tego, musimy zainstalować Visual Studio 2013 Update 2 oraz Visual Studio 2013 SDK.
Następnie w VS klikamy na Extensibility i Basic Scaffolder:
Dzięki SideWaffle zostaną wygenerowane podstawowe pliki potrzebne do stworzenia własnego szablonu w ASP.NET MVC 5. Dla podstawowej funkcjonalności wystarczy, że wyedytujemy kilka z nich.
Po wciśnięciu F5 uruchomi się Visual Studio experimental instance, co umożliwi nam debugowanie kodu. Jakbyśmy teraz chcieli skorzystać z naszego szablonu to zobaczymy tylko automatycznie wygenerowane nazwy, tzn.:
Po wciśnięciu Add ujrzyjmy automatycznie utworzone okno dialogowe:
Spróbujmy przejrzeć się wygenerowanym plikom i dodać jakąś własną logikę. Zacznijmy od klasy CustomCodeGeneratorFactory, w której możemy określić metadane:
private static CodeGeneratorInformation _info = new CodeGeneratorInformation( displayName: "Custom Scaffolder", description: "This is a custom scaffolder.", author: "Author Name", version: new Version(1, 0, 0, 0), id: typeof(CustomCodeGenerator).Name, icon: ToImageSource(Resources._TemplateIconSample), gestures: new[] { "Controller", "View", "Area" }, categories: new[] { Categories.Common, Categories.MvcController, Categories.Other });
Nie ma tutaj nic nadzwyczajnego. Proste informacje o autorze, nazwie szablonu itp:
private static CodeGeneratorInformation _info = new CodeGeneratorInformation( displayName: "Pierwszy szablon", description: "Moj pierwszy szablon.", author: "Piotr Zielinski", version: new Version(1, 0, 0, 0), id: typeof(CustomCodeGenerator).Name, icon: ToImageSource(Resources._TemplateIconSample), gestures: new[] { "Controller", "View", "Area" }, categories: new[] { Categories.Common, Categories.MvcController, Categories.Other });
Dużo ciekawszą klasą jest CustomCodeGenerator, która dziedziczy po CodeGenerator. Najważniejsze są tam dwie metody: GenerateCode oraz ShowUIAndValidate:
public interface ICodeGenerator { /// <summary> /// Performs code generation. /// /// </summary> void GenerateCode(); /// <summary> /// Gathers and validates information necessary for code generation. /// Any UI for the code generator should be displayed in this method. /// /// </summary> /// /// <returns> /// Return value indicates whether information was gathered and code /// generation can be performed. /// /// </returns> /// /// <remarks> /// Implement and return true, if scaffolder needs no user interface. /// /// </remarks> bool ShowUIAndValidate(); /// <summary> /// A list of NuGet packages upon which this /// code generator depends in order to run successfully. Packages listed here will be installed /// after ShowUIAndValidate succeeded and before the /// GenerateCode method is invoked. /// /// </summary> /// /// <returns> /// A list containing the NuGet package dependencies /// </returns> IEnumerable<NuGetPackage> Dependencies { get; } }
Domyślna implementacja ShowUiAndValidate wygląda następująco:
public override bool ShowUIAndValidate() { // Bring up the selection dialog and allow user to select a model type SelectModelWindow window = new SelectModelWindow(_viewModel); bool? showDialog = window.ShowDialog(); return showDialog ?? false; }
Innymi słowy, wyświetlane jest okno SelectModelWindow. Możemy oczywiście zastąpić to własnymi kontrolkami. Przykładowy SelectModelWIndow to nic innego jak okno WPF (XAML) i wygląda następująco:
<Window x:Class="BasicScaffolder3.UI.SelectModelWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" Height="176" Width="511" Title="Model Types"> <Grid> <Label Content="Choose a Model Type:" HorizontalAlignment="Left" Margin="36,39,0,0" VerticalAlignment="Top"/> <ComboBox HorizontalAlignment="Left" Margin="169,43,0,0" VerticalAlignment="Top" ItemsSource="{Binding ModelTypes}" DisplayMemberPath="DisplayName" SelectedItem="{Binding SelectedModelType, Mode=OneWayToSource}" Width="311"/> <Button Content="Add" IsDefault="True" HorizontalAlignment="Left" Margin="317,102,0,0" VerticalAlignment="Top" Width="75" RenderTransformOrigin="-0.187,0.75" Click="Button_Click"/> <Button Content="Cancel" IsCancel="True" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="405,102,0,0"/> </Grid> </Window>
Jak ktoś zna WPF to nie będzie miał z tym problemu. Z kolei domyślna implementacja GenerateCode to:
public override void GenerateCode() { // Get the selected code type var codeType = _viewModel.SelectedModelType.CodeType; // Setup the scaffolding item creation parameters to be passed into the T4 template. var parameters = new Dictionary<string, object>() { { /* This value should match the parameter in T4 */ "ModelType", /* This is the value passed */ codeType } //You can pass more parameters after they are defined in the template }; // Add the custom scaffolding item from T4 template. this.AddFileFromTemplate(Context.ActiveProject, "CustomCode", "CustomTextTemplate", parameters, skipIfExists: false); }
Szczególnie proszę zwrócić uwagę na:
this.AddFileFromTemplate(Context.ActiveProject, "CustomCode", "CustomTextTemplate", parameters, skipIfExists: false);
Wywołanie oznacza, że zostanie użyty szablon T4 (zachęcam do przeczytania tych wpisów). CustomTextTemplate wygląda następująco:
<#@ template language="C#" #> <#@ assembly name="System.Core" #> <#@ assembly name="EnvDTE" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ parameter name="ModelType" type="EnvDTE.CodeType" #> /// This code was generated by Basic Scaffolder. namespace <#= ModelType.Namespace.FullName #> { public class <#= ModelType.Name #>Controller { public void <#= ModelType.Name #>Method() { } } }
Oczywiście można tworzyć dowolną liczbę szablonów. Możliwe jest również dodanie statycznych plików za pomocą metody AddFile:
protected bool AddFile(Project project, string projectRelativePath, string sourceFilePath, bool skipIfExists);
Kolejna metoda to AddFodler, która oczywiście tworzy foldery:
protected ProjectItem AddFolder(Project project, string projectRelativePath);
Bardziej zawansowane szablony mogą korzystać z NuGet. Wystarczy przeładować właściwość Dependencies.
Myślę, że na wprowadzenie wystarczy. Szablony T4 to potężne narzędzie i za pomocą ich można wygenerować dowolny plik, dostosowany do każdego projektu.