ASP.NET MVC 5: Własny szablon

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:

image

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.

image

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

image

Po wciśnięciu Add ujrzyjmy automatycznie utworzone okno dialogowe:

image

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.

Leave a Reply

Your email address will not be published.