Site Meter
 
 

MvcScaffolding: Creating custom scaffolders

This blog post is part of a series about the MvcScaffolding NuGet package:

  1. Introduction: Scaffold your ASP.NET MVC 3 project with the MvcScaffolding package
  2. Standard usage: Typical use cases and options
  3. One-to-Many Relationships
  4. Scaffolding Actions and Unit Tests
  5. Overriding the T4 templates
  6. This post: Creating custom scaffolders
  7. Scaffolding custom collections of files

Intro

If you only ever want to generate controllers, actions, repositories, and the other item types built into MvcScaffolding, it’s enough just to customise the T4 templates for them. But what if you want to generate something different, for example configuration files, build scripts, CSS files, JavaScript files, or something else? Or what if you want to generate combinations of multiple interrelated items?

You can create a custom scaffolder that uses arbitrary PowerShell logic. It can render T4 templates (multiple ones if you want), with the result being output:

  • As a new file in your project
  • Or, as a new code block inserted into an existing class

Or, your PowerShell logic can use Visual Studio’s “code model” API to manipulate files and code elements in other arbitrary ways.

Example: Scaffolding jQuery Plugins

Note: The rest of this post assumes you’ve already got MvcScaffolding installed. If not, see the earlier posts in this series for an introduction.

Let’s say your team uses jQuery plugins all the time. Every separate bit of client-side functionality is implemented as a separate jQuery plugin. So, to speed this up and to gain better consistency, you want to make a scaffolder that generates the outline of a jQuery plugin. To get started, run the CustomScaffolder scaffolder, giving the name jQueryPlugin for your new scaffolder:

SNAGHTML2dbba9a7_thumb1

Yes, CustomScaffolder is a scaffolder that generates other scaffolders. I find this pleasing in a recursive kind of way.

This adds a CodeTemplates folder to your project, containing files for the new scaffolder:

image_thumb5

As you can see, we’ve got two files:

  • A PowerShell script (.ps1), where we can put arbitrary logic to decide what templates get rendered and where the output goes. By default, it renders a T4 template and uses the output to create a new file called ExampleOutput in the root of your project.
  • A T4 template (.t4), i.e., the thing that the default .ps1 file renders. By default this generates a simple C#/VB class (depending on your project type).

If you want to see this working, you can run the custom scaffolder right away:

Scaffold jQueryPlugin

This will generate a new class file, ExampleOutput.cs, in the root folder of your project. That’s really just to show you how it works. We don’t really want that, so don’t run the new scaffolder yet, or if you already have done, delete ExampleOutput.cs.

Making it do something useful

Here’s the design for our jQueryPlugin scaffolder:

  • It will take two parameters: PluginName (mandatory), and HasOptions (an optional switch that modifies the JavaScript code we generate).
  • It will render a T4 template, passing those parameter values to it. The template will produce the outline of a jQuery Plugin JavaScript file.
  • The output will create a new file, /Scripts/jQuery/jQuery.pluginname.js

To actually implement this, you either need a passing familiarity with PowerShell, or at least the willingness to play around with the default script and try to modify it to suit your requirements. It’s a bit out of scope for me to try to teach PowerShell in this blog post, so let’s skip to the completed implementation. Here’s my finished jQueryPlugin.ps1 file:

[T4Scaffolding.Scaffolder(Description = "Creates a jQuery Plugin code file")][CmdletBinding()]
param(
    [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)][string]$PluginName,
    [switch]$HasOptions,
    [string]$Project,
    [string]$CodeLanguage,
    [string[]]$TemplateFolders,
    [switch]$Force = $false
)
 
$outputPath = "Scripts\jQueryPlugins\jQuery.$PluginName.js"
 
Add-ProjectItemViaTemplate $outputPath -Template jQueryPluginTemplate `
    -Model @{ PluginName = $PluginName; HasOptions = $HasOptions.IsPresent } `
    -SuccessMessage "Added jQuery plugin at {0}" `
    -TemplateFolders $TemplateFolders -Project $Project -CodeLanguage $CodeLanguage -Force:$Force
 
Write-Host "---"
Write-Host "To reference your new jQuery plugin, copy the following script reference into your layout:"
Write-Host "<script src=`"`@Url.Content(`"~/$($outputPath.Replace("\", "/"))`")`" type=`"text/javascript`"></script>" -ForegroundColor DarkRed -BackgroundColor Gray
Write-Host "---"

I appreciate this may be a bit alien if you’ve never seen PowerShell before, but it’s not actually so bad:

  • The first few lines define metadata about the scaffolder itself – its description, and a definition of the parameters it takes (including saying which are mandatory/optional).
  • Add-ProjectItemViaTemplate is a cmdlet built into T4Scaffolding. It renders a T4 template (in this example, one called “jQueryPluginTemplate”) and creates a new file in your project containing the output (in this case, in the folder “Scripts\jQueryPlugins\”). You can pass whatever data you want from the PS1 script to the T4 template (in this example, we just pass the PluginName and HasOptions parameter values). See the end of this blog post for more info about T4Scaffolding’s built-in cmdlets.
  • Write-Host emits text into the console itself. In this example, we’re using it to supply useful information to the developer – an example <script> tag they can paste into their layout to reference the JavaScript file they just generated.

Next, we need the T4 template that defines the JavaScript code we want to generate. Here’s my finished jQueryPluginTemplate.t4 file. If you’ve written jQuery plugins before, this will probably look very familiar:

<#@ Template Language="C#" HostSpecific="True" Inherits="DynamicTransform" #>
// ----
// <#= Model.PluginName #> jQuery plugin
// ----
(function ($) {
    $.fn.<#= Model.PluginName #> = function (<# if(Model.HasOptions) { #>options<# } #>) {
<# if(Model.HasOptions) { #>
        var allOptions = $.extend({
            // TODO: Put default option values here
        }, options);
 
<# } #>
        return this.each(function () {
 
            var $this = $(this);
            // TODO: Add code here to operate on $this
 
        });
    };
})(jQuery);
Running the custom scaffolder

Now, you can invoke your custom scaffolder to generate jQuery plugins from the console. There’s full tab-completion support, so developers can auto-complete not just the name of your scaffolder (jQueryPlugin), but also the names of the custom parameters it takes.

SNAGHTML2dd907cd_thumb1[3]

This will generate a JavaScript source file at Scripts\jQueryPlugins\jQuery.reverseText.js, containing:

// ----
// reverseText jQuery plugin
// ----
(function ($) {
    $.fn.reverseText = function (options) {
        var allOptions = $.extend({
            // TODO: Put default option values here
        }, options);
 
        return this.each(function () {
 
            var $this = $(this);
            // TODO: Add code here to operate on $this
 
        });
    };
})(jQuery);

Once you’ve referenced this script file (e.g., by copy & pasting the example <script> tag emitted to the console), you can use your new plugin to operate on the HTML elements that match a jQuery selector, e.g.:

$(".someClass").reverseText();
A runnable example

Download the demo project, which contains the jQueryPlugin custom scaffolder. You can run it from the Package Manager console as follows:

Scaffold jQueryPlugin somePluginName             # Like this...
Scaffold jQueryPlugin somePluginName –HasOptions # ... or like this

Useful Scaffolding Cmdlets

In the preceding example, I demonstrated the use of Add-ProjectItemViaTemplate, a PowerShell cmdlet included in the core T4Scaffolding package (installed automatically when you install MvcScaffolding).

There are many other useful cmdlets included in T4Scaffolding that you can use from your custom scaffolders, for example to:

  • Render T4 templates
  • Inspect and modify files in your solution’s projects
  • Inspect and modify code elements in your classes
  • Detect primary keys and 1:many relations on entity classes
  • Interact with source control bindings
  • … or merely to pluralise words.

For a list of examples, see this wiki page on CodePlex.

Conclusion

With a bit of PowerShell-fu (or a bit of copy-paste-existing-examples-fu), you can make your own scaffolder that:

  • Takes a custom set of parameters
  • Renders a custom set of T4 templates
  • Sends the output to a custom set of locations in your project or in existing classes

Once you’ve made it, it’s easy to share with colleagues: just commit your /CodeTemplates folder into source control, and then anyone working on your project will be able to run your scaffolder

In the next post, I’ll present a slightly bigger example that does output multiple files, and produces a different type of controller with an Ajax-powered grid in its view.

MvcScaffolding: Overriding the T4 Templates

This blog post is part of a series about the MvcScaffolding NuGet package:

  1. Introduction: Scaffold your ASP.NET MVC 3 project with the MvcScaffolding package
  2. Standard usage: Typical use cases and options
  3. One-to-Many Relationships
  4. Scaffolding Actions and Unit Tests
  5. This post: Overriding the T4 templates
  6. Creating custom scaffolders
  7. Scaffolding custom collections of files

Previous posts in this series have shown how you can use MvcScaffolding’s built-in features to handle a range of CRUD scenarios, as well as quickly generate actions, view models, views, and unit test stubs using the included templates. But what if you want to do something different?

In this post and over the next two, I’ll show you how you can:

  • Change the code that the built-in scaffolders generate by overriding the T4 templates they use
  • Scaffold completely different types of things by creating your own new, custom scaffolders with arbitrary PowerShell logic
  • Combine multiple built-in and custom scaffolders to generate whole structures of code files that implement useful functionality

Overriding the T4 Templates

Note: This post assumes you’ve already installed the MvcScaffolding package. If not, see the earlier posts in this series for an introduction.

The simplest form of customisation is overriding the T4 templates. For example, if you want to change what data access code is generated, you can supply your own templates that produce code based on NHibernate, or Mongo, or whatever, instead of Entity Framework which is the default.

To get started, you first need to know which existing T4 template you want to override. You can find this out either by:

  • Looking in <yourSolutionDir>\packages\<scaffolderPackage>\tools to see the structure of scaffolders and templates
  • Or, by running the scaffolder whose output you want to change and passing the –Verbose flag so it tells you which templates it’s using
  • Or, just by looking at the following table to see the built-in ones:
Scaffolder name Template name What it’s used for
DbContext DbContext Empty data context class
DbContext DbContextEntityMember Entity set property used for each model type added to the data context class (e.g., DbSet<YourModel> properties)
Repository Repository Repository interface and default concrete implementation
Action Action Action method (Note: only used when generating standalone actions. Not used by controller scaffolder)
Action ActionPost As above, for actions that handle POST requests
Action ViewModel View model used by scaffolded actions
ActionUnitTest TestClass Empty unit test class
ActionUnitTest TestMethod Unit test method stub
View Create, _CreateOrEdit, Delete, Details, Edit, Empty, Index MVC view for the corresponding view type
(Note: CreateOrEdit has no leading underscore if you’re generating ASPX views, because that’s a Razor convention)
Controller ControllerWithContext Complete MVC controller with inline data access code
Controller ControllerWithRepository Complete MVC controller that uses repositories for data access

Next, run the special “CustomTemplate” scaffolder to create a template override. The syntax is: “Scaffold CustomTemplate scaffoldername templatename”. For example, if you want to override the repository template, the scaffolder name is “repository”, and so is the template name, so you run:

SNAGHTML2d9deaf9_thumb[1]

This adds a new CustomScaffolders folder into your project which contains the template override:

image_thumb[2]

Now all you have to do is edit that file, Repository.cs.t4, using T4 syntax to define the code you want to generate. You can use T4’s <#= … #> blocks to evaluate parameter values supplied by the scaffolder to insert things like the repository name, the model type, the model primary key property name, and so on. The file’s initial contents acts as an example you can modify.

For example, if you wanted repositories implemented as static in-memory collections, instead of being backed by a SQL database, you could set up the first few lines of the class template as follows:

/// <summary>
/// A dummy in-memory repository for <#= modelName #> entities. Its data will be reset each time you restart the application.
/// </summary>
public class <#= modelName #>Repository : I<#= modelName #>Repository
{
    private readonly static Dictionary<<#= primaryKeyProperty.Type.AsString #>, <#= modelName #>> InMemoryCollection = new Dictionary<<#= primaryKeyProperty.Type.AsString #>, <#= modelName #>>();
 
    public IQueryable<<#= modelName #>> All
    {
        get { return InMemoryCollection.Values.AsQueryable(); }
    }

… and of course edit the rest of the template to read/write data in InMemoryCollection too.

Using Your Custom Template

You don’t have to do anything special to use the custom template. Just the fact that it’s there in your project means it will override the default one. So, next time you run “Scaffold Repository SomeEntity” (or scaffold a controller using the –Repository switch), it will use your template.

Of course, this means it’s easy to share: just commit your /CodeTemplate folder into source control, and then your template becomes a standard for everyone working on your project.

A runnable example

For reference, see the downloadable example project that includes a complete “in-memory collection repository” template. Try it out by generating a controller with repository for the provided StockItem model:

Scaffold Controller StockItem -Repository

Then, you can compile and run the application, navigate to the URL /StockItems, and add/edit/delete items in the in-memory collection.

If you’d like to do something a little more interesting, how about adapting the repository template further, to generate code that reads/writes data using NHibernate or Mongo or some other data store?

Scaffolding Actions and Unit Tests with MvcScaffolding

This blog post is part of a series about the MvcScaffolding NuGet package:

  1. Introduction: Scaffold your ASP.NET MVC 3 project with the MvcScaffolding package
  2. Standard usage: Typical use cases and options
  3. One-to-Many Relationships
  4. This post: Scaffolding Actions and Unit Tests
  5. Overriding the T4 templates
  6. Creating custom scaffolders
  7. Scaffolding custom collections of files

So, if you’ve read any of my previous posts about MvcScaffolding, you’ll know that you can use it for “CRUD” scenarios: i.e., making create, read, update, delete pages so that users can enter and edit data. Scaffolding and CRUD go together so often that people get the impression that scaffolding is only about CRUD. Well, it’s not. You can do a lot more with scaffolding.

In this post I’ll show you how to use MvcScaffolding to quickly add action methods to controllers (along with their views and view models), and to scaffold unit test stubs for them.

Hang on,” you might be thinking, “I can already add action methods and unit tests. I just type them into Visual Studio.” Yes, yes, of course you can. But this way is faster, and lets you set up and share your own templates for them. Like snippets, but with more command-line smarts.

Installing or Updating MvcScaffolding

First, open an ASP.NET MVC 3 project, open the Package Manager Console, and then install the MvcScaffolding package, being sure to get version 0.9.8 or later:

Install-Package MvcScaffolding

If your project already has a version of MvcScaffolding older than 0.9.8, update it like this:

Update-Package MvcScaffolding

Note: If you’re updating from an older version, this will prompt you to restart Visual Studio before continuing the installation. Obey it, or badness will happen! So, after restarting VS, run “Install-Package MvcScaffolding” again. This time it won’t prompt you to restart VS.

Scaffolding Actions

To add a ContactMe action to your HomeController, run the following scaffolding command:

Scaffold Action Home ContactMe

What will happen? HomeController will have a new method:

public class HomeController : Controller
{
    // ... existing code not affected ...
 
    public ViewResult ContactMe()
    {
        return View();
    }
}

… and there’ll be a view for ContactMe at /Views/Home/ContactMe.cshtml.

Excellent. That was slightly quicker than adding the action and view by hand. But I almost always want to pass a view model from my actions to their views, so there’s a clean, strongly-typed way of transferring action-specific data. No problem. There’s a switch for that.

Delete the ContactMe action and view (or use the –Force) switch, and rescaffold as follows:

Scaffold Action Home ContactMe –WithViewModel

What happens?

SNAGHTML3efc96dd

This time it’s added an action that uses a view model:

public class HomeController : Controller
{
    // ... existing code not affected ...
 
    public ViewResult ContactMe()
    {
        return View(new ContactMeViewModel {
            // Populate properties here
        });
    }
}

… and created a view model at /Models/ContactMeViewModel.cs:

public class ContactMeViewModel
{
    // Add properties here
}

… and made the view, /Views/Home/ContactMe.cshtml strongly typed for the ContactMeViewModel class.

Or, if you prefer the view model class to be called ContactInfo, use the –ViewModel parameter instead of the –WithViewModel switch:

Scaffold Action Home ContactMe -ViewModel ContactInfo

If there’s already a ContactInfo class, it will use it (wherever it is), or if not it will create a new one in /Models.

Now you can go ahead and add suitable properties to the view model class and reference them in both the action and the view. In total, this saves a few more seconds compared to creating all those files by hand.

Scaffolding Post-handling Actions

OK, so you’ve created your basic action. Maybe you’ve added some properties to the view model, like this:

public class ContactInfo
{
    [Required] public string Name { get; set; }
    [Required] public string EmailAddress { get; set; }
}

… and edited the view, /Views/Home/ContactMe.cshtml to display a form:

@model ActionScaffoldingExample.Models.ContactInfo
@{ ViewBag.Title = "ContactMe"; }
 
<h2>Contact Me</h2>
<p>Give us your contact details, and we'll, well, contact you. Obviously.</p>
 
@using (Html.BeginForm()) {
    @Html.EditorForModel()
    <input type="submit" />
}

Next, you need some action to receive the posted data. You could create it by hand like normal. Or, you could scaffold it, like this:

Scaffold Action Home ContactMe -ViewModel ContactInfo -Post

This adds the following action method (without affecting your existing code):

[HttpPost, ActionName("ContactMe")]
public ActionResult ContactMePost(ContactInfo contactinfo)
{
    if (ModelState.IsValid) {
        return RedirectToAction("Index");
    } else {
        return View(contactinfo);
    }
}

You can go ahead and edit this to put in some code to send an email, save the data to a database, or whatever. But right away you’ve got the basics of handling form posts, slightly quicker than typing it out by hand.

Scaffolding Unit Test Stubs

If you’re writing unit tests, you probably need all the help you can get with making the process more streamlined. The benefits of unit testing are high, but so are the costs, so let’s try to reduce the costs a bit.

Do you already have a unit test project in your solution called “{YourMvcProjectName}.Test” or “{YourMvcProjectName}.Tests”? If not, you can add one by right-clicking your solution name in Solution Explorer, choosing Add->New Project, and choosing “Test Project” (use “Search Installed Templates” if you can’t find that). To follow the naming convention, if your main project is called MySuperApp, call the test project MySuperApp.Test or MySuperApp.Tests. Finally, add a reference from the new test project to System.Web.Mvc and to your main MVC application project.

Now you can run the following scaffolding command:

Scaffold UnitTest Home ContactMe

What will this do?

  • If you don’t already have a HomeControllerTest.cs class file in your test project, it will create one
  • Then it will add a unit test stub for your ContactMe action:
    [TestClass]
    public class HomeControllerTest
    {
        [TestMethod]
        public void ContactMeTest()
        {
            // Arrange
            var controller = new HomeController();
 
            // Act
            var viewResult = controller.ContactMe();
            var viewModel = viewResult.Model;
 
            // Assert
            // Assert.AreEqual("expected value", viewModel.SomeProperty);
            Assert.Inconclusive(); // Todo: Make assertions, then remove this line
        }
    }

Add some assertions, remove the “Assert.Inconclusive” line (which is there to make sure you don’t just leave the stub code as-is and think you’ve actually got a passing test) and you’re away.

If you will be writing assertions about property values on your view model class, you can optionally specify the view model class name. Either write:

Scaffold UnitTest Home ContactMe –WithViewModel

… to use the default view model name, or

Scaffold UnitTest Home ContactMe –ViewModel ContactInfo

… to specify a custom view model class name. If you do either of these, the unit test stub will be set up to require the ViewResult to have a Model value of the right type:

// Act
var viewResult = controller.ContactMe();
var viewModel = (ContactInfo)viewResult.Model;

Now you can easily add assertions based on this view model type.

Scaffolding Unit Test Stubs for Post-handling Actions

If you want a unit test to specify how your post-handling action should behave, just specify the post-handling action by name. For example,

Scaffold UnitTest Home ContactMePost

This will generate the following test stub:

[TestMethod]
public void ContactMePostTest()
{
    // Arrange
    var controller = new HomeController();
 
    // Act
    var actionResult = controller.ContactMePost(null);
 
    // Assert
    Assert.Inconclusive(); // Todo: Make assertions, then remove this line
}

If you want to write a test to specify how the action handles particular ContactInfo parameter values, construct a ContactInfo instance in the test and replace the “null” in “controller.ContactMePost(null)” with your instance variable. Of course, this will not test model binding (including validation that happens during model binding), as that’s a separate concern.

Notice that this time, the generated code doesn’t include anything to do with a ViewResult. Why is this? The template for test methods inspects the return type of your action method, and produces different code depending on that type.

  • If your method returns a ViewResult, the test will automatically extract the Model value (cast to your view model type, if given), so you can write assertions against it
  • If your method returns a RedirectToRouteResult, the test will automatically grab the RouteData values so you can write assertions about them
  • If your method returns a general ActionResult or something else, as in the last example, the test will just obtain the action result instance and it’s up to you what to do with it.

You can customize this logic by customizing the template if you want (details below).

And what if you don’t want to use MSTest, but would rather use NUnit or something else? That’s also a matter of customizing the templates.

Scaffolding Actions *with* Unit Tests in One Shot

If you want both the action and a test stub for it in a single command, try this:

Scaffold MvcScaffolding.ActionWithUnitTest Home SomethingElse -WithViewModel

This one command gives you an action called SomethingElse, a view model class called SomethingElseViewModel, a view (strongly-typed), a unit test class, and a unit test method stub (set up to work with the generated view model class). Surely that is saving you several seconds at least.

If you wanted to establish the standard that all scaffolded actions should have unit tests, write this:

Set-DefaultScaffolder Action MvcScaffolding.ActionWithUnitTest

Now, “Scaffold Action” will map to the MvcScaffolding.ActionWithUnitTest scaffolder, instead of mapping to MvcScaffolding.Action as it does by default.

Customizing the Templates

I’m about to write a whole post about customizing the templates, but in the meantime, here’s a sneak preview. Try any of the following:

Scaffold CustomTemplate MvcScaffolding.Action Action
Scaffold CustomTemplate MvcScaffolding.Action ActionPost
Scaffold CustomTemplate MvcScaffolding.Action ViewModel
Scaffold CustomTemplate MvcScaffolding.ActionUnitTest TestClass
Scaffold CustomTemplate MvcScaffolding.ActionUnitTest TestMethod

Then edit the T4 templates that appear in your project’s /CodeTemplates/Scaffolders folder.

Then run the scaffolding commands again (remember to pass –Force if you want to overwrite existing files).

See the difference.

To share your templates overrides with others on your team, simply commit them into source control.

Conclusion

For people who enjoy the power of the Package Manager Console command line, scaffolding actions and unit tests is a fast and customizable way of adding arbitrary code to your project.

In a future post, I’ll show how to create entirely new scaffolders of your own that apply custom PowerShell logic and can add any combination of files and class members, rather than just customizing T4 templates.