Site Meter
 
 

MvcScaffolding: Scaffolding custom collections of files

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. Creating custom scaffolders
  7. This post: Scaffolding custom collections of files

Previously you’ve seen how to modify the output from existing scaffolders (by overriding their T4 templates) and how to create custom scaffolders that take arbitrary parameters and render new templates of their own.

But what if, instead of generating just a single file, you want to generate multiple files that all work together? Well, it’s not surprising that you can do so.

  • You can put multiple Add-ProjectItemViaTemplate commands in your PowerShell file. You can generate as many different files, using as many different templates, as you want.
  • You can invoke one scaffolder from another. For example your custom scaffolder logic can contain “Scaffold Controller” or “Scaffold View” instructions to generate controllers and views using the built-in logic.

In this final post in the current MvcScaffolding series, I’ll show how some of these ideas can be tied together to build something useful.

Example: An Ajax-powered Controller

I showed an example of doing this in my MvcConf talk (video here) a couple of months ago, when I built a custom ControllerWithAjaxGrid scaffolder. This custom scaffolder produces a controller (obviously), but instead of having the usual index/edit/details/delete views, it just has a single view that contains an Ajax-powered grid from which the user can view, add, edit, and delete records. Videos are hard to reuse code from, so for your convenience I’ll reproduce the code in this blog post.

To see it in action, download the demo project, and then scaffold an Ajax-grid controller for the included StockItem model by executing “Scaffold ControllerWithAjaxGrid StockItem” in the console:

SNAGHTML2e143ef5

Run the controller by launching the application and navigating to /StockItems:

image

It uses jqGrid to do all the client-side stuff. By the way, if you’re wondering how to make jqGrid do different things like sorting or filtering, don’t ask me, I have no idea – talk to the experts! Let’s keep this post focused on the scaffolding side of things.

How ControllerWithAjaxGrid Works

I won’t list all the code for ControllerWithAjaxGrid and its template – you can download the runnable demo project for that. As an outline of how it works, ControllerWithAjaxGrid.ps1 contains the following instruction to create the controller itself:

Scaffold MvcScaffolding.Controller $ModelType -Project $Project `
    -CodeLanguage $CodeLanguage -OverrideTemplateFolders $TemplateFolders `
    -NoChildItems -Force:$Force

This inherits all the normal controller-scaffolding logic (e.g., knowing which folder to output the class file into) from MvcScaffolding.Controller, and merely overrides location where the T4 template is found (using –OverrideTemplateFolder). I was then able to put a ControllerWithContext.cs.t4 file in my custom scaffolder’s folder and hence override the code that gets generated. My custom controller template returns JsonResult data to support the Ajax grid. For example, here’s the template for its GetData action:

public JsonResult GridData(int rows, int page)
{
    var count = _context.<#= modelNamePlural #>.Count();
    var pageData = _context.<#= modelNamePlural #>.OrderBy(x => x.<#= Model.PrimaryKey #>).Skip((page - 1) * rows).Take(rows);
    return Json(new {
        page = page,
        records = count,
        rows = pageData,
        total = Math.Ceiling((decimal)count / rows)
    }, JsonRequestBehavior.AllowGet);
}

Similarly, my custom controller doesn’t need all the usual views – it only needs one that contains the JavaScript code to display the grid. I generate that view using a “Scaffold View” command in ControllerWithAjaxGrid.ps1, again using the –OverrideTemplateFolders parameter to make the View scaffolder load the T4 template from ControllerWithAjaxGrid’s folder.

One interesting thing about the view template is that it gets a list of all the properties on your model class:

<# var properties = viewDataType.VisibleMembers().OfType<codeProperty>(); #>

… then later uses this to generate some JavaScript code relating to each property:

<script type="text/javascript">
    jQuery("#ajaxGrid").jqGrid({
        ...
        colNames: [<#= string.Join(", ", properties.Select(prop => "'" + prop.Name + "'")) #>],
        ...
    });
</script>

Finally, my custom controller needs to be sure that there is actually a data context class that it can use for data access. This is easy to arrange: I just put the following command in ControllerWithAjaxGrid.ps1:

Scaffold DbContext $ModelType -DbContextType ((Get-Project $Project).Name + "Context")

You can see I’m using the convention that the data context class should be called ProjectNameContext, but you could change that if you wanted.

For the full code including the T4 templates, download the demo project.

Conclusion

You could use the same techniques to create other custom controller types with different combinations of views, data access classes, and so on.

If you’re unfamiliar with PowerShell then it might take a bit of learning, but once you’ve got a custom scaffolder to suit your project’s requirements, you can commit your /CodeTemplates folder into source control, and then everyone on your team can quickly generate controllers in the style you’ve defined, supplying whatever custom parameters you’ve declared.

For more information about creating custom controllers, see the tutorial in my previous blog post and the list of useful cmdlets for custom scaffolder scripts.

32 Responses to MvcScaffolding: Scaffolding custom collections of files

  1. Vladan Strigo

    MvcScaffold is one of the biggest additions in my mind for the MVC toolset…really thanks for the GREAT work!

    Vladan

  2. I get a 404 when I try to download the demo project.

  3. Steve

    Khalid – sorry, typo in the URLs. Fixed now.

  4. lynn

    Steve, I was able to do something like this involving controllers, models, .js and .cshtml files (over 30 files and growing) and it was fairly easy.

    I really like the work y’all did here. This is great! :)

  5. Ok this might sound like a crazy idea, but is there any way to pre-render a partial view in a t4 template?

    Let’s say you already had a view for a car. I want to read that view in and render it to get the html. Then i want to parse that html and put my placeholders in. Is that possible?

  6. Pingback: The Morning Brew - Chris Alcock » The Morning Brew #831

  7. @Khalid it might be easier to do the whole thing in powershell and forget trying to incorportate a T4 template. It requires a little more writing of code but gives you total control over the output.

  8. @Christopherusa

    Hey Steve, great blog, and great talk about scaffolding at mix11.

  9. wil

    How can I change the saved location for repository files?

    I would like to have repository file to go to repository folder.

    Thanks for this tool!

  10. Steve,

    This is an amazing series. As you suggested in one of your talks, this could help implementing best practices for developers in a team environment while increasing productivity tremendously.

    Thanks
    Deepak

  11. Asti

    Dude, this is fucking brilliant.
    So. Much. Pain. avoided.

  12. docfoster

    I find the T4 templates can be quite tricky to workout, and debug. i ended up using an mvc generator as a starting point which saved me alot of hassle it was at mvc3razor.com

  13. Paul Duyker

    Hi Steve,

    Is there a way to have the Repositories go to a different project?

    Thanks heaps

  14. Ardalan

    Steve, I have been waiting for this tool for a very very long time… No more mvc headaches. I’ll call this MvcAdvil:)
    Thank you for the great work!

  15. Super series Steve! before this scaffolding was a mystery, you solved it one clue at a time. I am currently developing a scaffold to use the Telerik MVC extensions, will blog about it once done…
    Thanks again.

  16. Leonard

    using VWD and got to this step w/o much trouble. This message has me stumped. Updated package to get this far. Showing T4 1.0.1 , scaffold 1.0.0

    PM> Scaffold ControllerWithAjaxGrid StockItem
    Invoke-Scaffolder : A positional parameter cannot be found that accepts argumen
    t ‘StockItem’.
    At line:1 char:9
    + Scaffold <<<< ControllerWithAjaxGrid StockItem
    + CategoryInfo : InvalidArgument: (:) [Invoke-Scaffolder], Parame
    terBindingException
    + FullyQualifiedErrorId : PositionalParameterNotFound,T4Scaffolding.Cmdlet
    s.InvokeScaffolderCmdlet

  17. Leonard

    Error message didn’t indicate the problem. Near as I can tell, Unblocking the scripts fixed most of my problem.

    This showed up in the StockItemsController:

    private System.__ComObject _context = new System.__ComObject();

    Took a wild guess and replaced System.__ComObject with the name of the DbContext.

    I have a grid and the book is on order.

  18. When setting up an MVC 3 application, the foreign keys that should allow drop down lists to select an item do not get rendered as drop downs, but as static inputs. This can be resolved by creating a custom display and view for that field.
    We will need to start by creating a custom partial view that will live in “~/Views/Shared/DisplayTemplates/UserGuid.cshtml”, and “~/Views/Shared/EditTemplates/UserGuid.cshtml”. The code for one is located below:
    @model Guid

    @{
    incMvcSite.Models.MvcSiteDB db = new incMvcSite.Models.MvcSiteDB();
    incMvcSite.Models.SecUser usr = db.SecUsers.Single(u => u.Guid == Model);
    }
    @usr.Display

    This is a display for template that will look up the item in the referenced table and display it. We also need an edit for template as follows:

    @model Guid
    @{
    incMvcSite.Models.MvcSiteDB db = new incMvcSite.Models.MvcSiteDB();
    SelectList items = new SelectList(db.SecUsers.OrderBy(i => i.Display).ToList(), “Guid”, “Display”, Model);
    }
    @Html.DropDownList(“”, items)

    The edit for template is implemented as a drop down list. Originally, we has used static HTML code, but the problem will appear of implementing a “prefix”. Static HTML code does get handled by HTML helpers, so it’s recommended that you use the HTML.DropDownList().
    To force the MVC framework to use the new Display and Edit for templates, we need to annote our model item an add the following line:

    [UIHint("UserGuid")]

    This will cause MVC to use the Display and Edit templates named “UserGuid”, which are just partial views.

  19. mvcvm

    Steve,

    I have reported this on Connect (id 684926).

    Do you find it interesting that I get an error in TeamController.Index() when the DbContext is named TeamContext, but renaming TeamContext to DefaultConnection solves the problem?

    It only happens on WIn 7, not on XP .

    I am using these bits:
    VS 2010 SP1
    New C# Mvc3 Razor Project.
    NuGetPackage Manager 1.4.20701.9038
    System.Web.Providers 1.0
    SqlServerCompact 4.0.8482.1
    EntityFramework 4.1.10715.0

    I get this first:
    CREATE DATABASE permission denied in database ‘master’ at

    public ViewResult Index()
    {
    // error here
    return View(db.Teams.ToList());
    }

    I just need to rename the DbContext and the error goes away!

    From:
    private TeamContext db = new TeamContext();
    To:
    private DefaultConnection db = new DefaultConnection();

    I am using SQL Compact with a .sdf file and full perms on APP_DATA:

    < add name=”DefaultConnection”
    connectionString=”Data Source=|DataDirectory|Test.sdf;”
    providerName=”System.Data.SqlServerCe.4.0″ >

    I was uninstalling SQLExpress and messing with dbo, dbcreator etc until i realised it wasn’t necessary to workaround the bug.

    ;-) Just wanted to get your reaction!

  20. Ron

    Having the same issue as Leonard:

    PM> Scaffold ControllerWithAjaxGrid StockItem
    Invoke-Scaffolder : A positional parameter cannot be found that accepts argument ‘StockItem’.
    At line:1 char:9
    + Scaffold <<<< ControllerWithAjaxGrid StockItem
    + CategoryInfo : InvalidArgument: (:) [Invoke-Scaffolder], ParameterBindingException
    + FullyQualifiedErrorId : PositionalParameterNotFound,T4Scaffolding.Cmdlets.InvokeScaffolderCmdlet

    I wasn't able to discern what Leonard meant by "Unblocking the scripts"

    Any help would be appreciated.

    Thanks,
    Ron

  21. Buddy Stein

    Saves me tons of time. Brilliant.

  22. Marcelo

    Great work steve!

    I’m waiting for more samples and updates.

  23. Pingback: ASPNET MVC 3 scaffolding with ajaxgrid or jqgrid

  24. Nandu

    Will Scaffold work on n-tier application? I have my domain objects in a separate project. How can use scaffolding in this scenario

  25. Pavel Nuñez

    Same problem:

    PM> Scaffold Controller Client
    Invoke-Scaffolder : A positional parameter cannot be found that accepts argument ‘Client’.
    At line:1 char:9
    + Scaffold <<<< Controller Client
    + CategoryInfo : InvalidArgument: (:) [Invoke-Scaffolder], ParameterBindingException
    + FullyQualifiedErrorId : PositionalParameterNotFound,T4Scaffolding.Cmdlets.InvokeScaffolderCmdlet

    Whats happening? I have:

    Successfully installed 'EFCodeFirst 0.8'
    Successfully installed 'T4Scaffolding 0.8.0'
    Successfully installed 'MvcScaffolding 1.0.6'
    Successfully installed 'SqlServerCompact 4.0.8482.1'
    Successfully installed 'WebActivator 1.0.0.0'
    Successfully installed 'EFCodeFirst.SqlServerCompact 0.8.8482.1'

    Can anyone tell me why I cant get Scaffold working for my domain model 'Client'?

  26. That is pretty cool. Can’t wait for more

  27. nathan

    Hi,Steven:
    Thanks for your great work!
    I have a question:
    does this work for n-tier application?

  28. Peterch

    Although I succeeded with Scaffolding earlier posts, in this poste I got the same message as Pavel Nuñez, Ron and Leonard:
    ParameterBindingException, PositionalParameterNotFound

    using PM> Scaffold ControllerWithAjaxGrid StockItem

    (Pavel is using Scaffold Controller …)

    MVCScaffolding.ControllerWithAjaxGrid.ps1 is found in CodeTemplates\Scaffolders\ControllerWithAjaxGrid

    Is it a problem with T4Scaffolding.Cmdlets.InvokeScaffolderCmdl ?

  29. Peterch

    Stevens don’t seem to check comments since April 2011. So I’ve rolled up my sleeves and got to work…
    I’m using VS2010, Windows 7 premium, and the latest nugets:
    EntityFramework.4.1.10715.0
    MvcScafolding.1.0.6
    T4Scaffolding.1.0.5 —-> Pavel may update his version before testing !

    1) I see the following comment in MvcScaffolding.Controller.ps1 and T4Scaffolding.EFRepository.ps1 :
    # Ensure you’ve referenced System.Data.Entity
    So ensure it yourself in your model classes !

    2) This message is recurent:
    “Get-ProjectType: Cannot find a type matching the name ‘CustomControllerScaffolderExampleContext’. Try specifying the fully-qualified type name, including namespace”
    So I guess that the DBContext is accessed by the command Scaffold Controller, before it is created in Scaffold.DbContext.

    In ControllerWithAjaxGrid.ps1, I put the Scaffold DbContext line just after the param part – e.g. about line 10 – BEFORE the Scaffold MvcScaffolding.Controller line.

    You MUST save the ps1 file before issuing
    PM > Scaffold ControllerWithAjaxGrid StockItem
    It doesn’t get saved automatically… bad Visual Studio !

    3) You can now run
    PM > Scaffold ControllerWithAjaxGrid StockItem
    Added database context ‘Models\CustomControllerScaffolderExampleContext.cs’
    Added ‘StockItem’ to database context ‘ScaffoldingCustomizationExamples.Models.CustomControllerScaffolderExampleContext’
    Scaffolding StockItemController…
    Added controller Controllers\StockItemController.cs
    Added Index view at ‘Views\StockItem\Index.cshtml’

    But another change is necessary in StockItemController which contains this line (check above comment by Leonard)
    private System.__ComObject _context = new System.__ComObject();
    Replace with:
    private CustomControllerScaffolderExampleContext _context = new CustomControllerScaffolderExampleContext();

    4) Now ENJOY !

    Maybe somebody can point out how to correct the point (3) in the code? Can’t find “_context” in the T4/PS1 files ! Maybe in the MvcScaffolding Dll?
    And just to be “clean” the $Project variable has this incorrect value “CustomControllerScaffolderExample”.

  30. Peterch

    Found how to remove the error in StockItemController: it’s in
    … Projects\CustomControllerScaffolder\CustomControllerScaffolder\CodeTemplates\Scaffolders\ControllerWithAjaxGrid\ControllerWithContext.cs.t4
    Replace this line:
    private _context = new ();
    By this line
    private _context = new ();

    The new line comes from ControllerWithContext.cs.t4 !

    So the scaffolding works as advertised NOW… and it’s really coooool ! Thanks Steve !

  31. Peterch

    Sorry, the code containing tags didn’t get through:
    Replace this line
    private &lt#= Model.DbContextType #> _context = new &lt#= Model.DbContextType #>();
    By this:
    private <#= ((CodeType)Model.DbContextType).Name #> _context = new <#= ((CodeType)Model.DbContextType).Name #>();

    ;-)

  32. I’m REALLY breaking my brain trying to figure this one out and my Google-Fu apparently isn’t getting the job done.

    Scaffolding is great. I’ve got one issue with it. I cannot seem to, for any reason whatsoever, get an enum to be included in the scaffolding of the views.

    Example: I had a drop-down field I’m trying to get month selected. Instead of creating a month model, scaffolding the entire thing, and entering each month specifically, I wanted to create an enum with each value and let the scaffolding include a drop-down with each one of them and then store that value in the db. HOW do I do that?

    Also, I wanted to set up a receipt function that would enter a lot of customer data and store it for a purchase, and at the top, I wanted to have four radio buttons that selected the types of receipts. Sales, Authorization, Voice authorization and Refund. Not that difficult, or so I thought. And getting scaffolding to generate a radiobutton group seems to be like pulling teeth.

    Steven, your video on scaffolding from MVCConf has got me this far, but I need help to get to the next level.

    Thank you.