Asp.Net MVC2 maatwerk T4 templates

Asp.Net MVC rust voornamelijk op conventies en veel minder op XML
configuratie. De configuratie wordt niet meer afgedwongen met XSD’s. Dit maakt het voor ontwikkelaars prettiger om te ontwikkelen want dit is dus WYSIWYG de architectuur ontwikkelen (de project mappenstructuur is de architectuur) maar we moeten ook de ‘onzichtbare’ conventies leren kennen.

Do not want to catch fire

De belangrijkste conventie is de synchronisatie tussen controller
methodes (ook wel actions genaamd) en de bijbehorende views. Gelukkig heeft
Visual Studio 2010 standaard ondersteuning voor het genereren van Views en
Controllers via templates. Dit neemt veel werk uit handen want het uittypen van
een view is veel werk en foutgevoelig.

Daarom gebruiken we de Add View en Add Controller wizard.

Add View met standaard templates

Maar helaas zijn de gegenereerde templates niet echt maatwerk. Zo
ontbeert de standaard Controller template de ondersteuning van een repository
(zie ook mijn vorige blog) . En ook zijn er standaard geen ActionFilters
voor het opvangen van foutmeldingen en Authorisatie.

Ook aan de gegenereerde Views valt wel wat aan te merken. Ik vind het wel
prettig als de cursor al op het eerste invoerveld is geplaatst. Ook zou ik
iedere submit graag nogmaals door de gebruiker bevestigd willen zien.

Bij Views met een HTML Table zou ik graag een correcte THead en een TBody
gebruikt zien worden. Dan wordt de header regel niet meegeteld bij de zebra
formattering (de even rijen krijgen dan een andere achtergrondkleur dan de
oneven rijen.)

Ik gebruik hiervoor nog al wat zelf geschreven jQuery code en/of jQuery
plugins (zoals de confirm plugin  en een gesorteerde tabel)

Ik wilde dus mijn templates aanpassen en dat blijkt heel goed mogelijk te
zijn! Ik kwam er achter via de vermakelijke en zeer onderhoudende presentatie
Microsoft ASP.NET Model View Controller (MVC): Ninja on Fire Black Belt Tips

Dus ik heb dit eens geprobeerd en inderdaad, het werkt echt fantastisch!
En het is een klusje van slechts enkele minuten!


He ho, let’s go!

Zorg dat je het installatiepad van Visual Studio 2010 kent. Ga dan
naar:

  • [drive]\Program Files\Microsoft Visual Studio
    10.0\Common7\IDE\ItemTemplates\CSharp\Web\MVC 2\CodeTemplates\AddController

of

  • [drive]\Program Files\Microsoft Visual Studio
    10.0\Common7\IDE\ItemTemplates\VisualBasic\Web\MVC 2\CodeTemplates\AddController
    .

Deze zal wel voor de VB.Net collega’s zijn?

Maak daar een copy van de controller template:

  • naar [project]\ CodeTemplates\AddController\Controller.tt


Let op
: er is maar 1 controller template. Wees dus  zuinig op het origineel.

Ga daarna naar:

  • C:\Program Files\Microsoft Visual Studio
    10.0\Common7\IDE\ItemTemplates\CSharp\Web\MVC 2\CodeTemplates\AddView

En maak een copy van de view templates (we houden de keuze naar
de originelen dus even refereren):

  • naar [project]\ CodeTemplates\AddView\ Create.tt
  • naar [project]\ CodeTemplates\AddView\ Details.tt
  • naar [project]\ CodeTemplates\AddView\ Edit.tt
  • naar [project]\ CodeTemplates\AddView\ List.tt

Maak tevens een copy van iedere view template want deze gaan we
aanpassen:

  • naar [project]\ CodeTemplates\AddView\ ProjectCreate.tt
  • naar [project]\ CodeTemplates\AddView\ ProjectDetails.tt
  • naar [project]\ CodeTemplates\AddView\ ProjectEdit.tt
  • naar [project]\ CodeTemplates\AddView\ ProjectList.tt

We zijn al op de helft. Voeg nu eens voor de grap een view toe aan je
project:

Add View met extra templates

Zie je dat de nieuwe View templates al direct te selecteren zijn?

Maar nu gaan we de inhoud aanpassen. Voeg eerst nog even T4 IntelliSence
ondersteuning toe aan VS2010 via de Extension Manager:

T4 Editor extension

Zie ook http://tangibleengineering.blogspot.com/2009/05/tangible-t4-editor-now-listed-in-visual.html

In deze blog wordt beperkt ingegaan op T4. Voor wie echt in T4 wilt  duiken kan beter doorverwezen worden naar deze link.

Ok, we hebben nu dus een T4 editor binnen Visual Studio 2010 beschikbaar  met IntelliSence.

 Klaar!

Maar waar waren we gebleven. O ja, dus we willen Repository ondersteuning
binnen de Controller:

<#@ template language="C#" HostSpecific="True" #>
 <#
 MvcTextTemplateHost mvcHost =
 (MvcTextTemplateHost)(Host);
#>
...

using System.Web.Mvc;
using MyProject.Models;
namespace <#= mvcHost.Namespace #>
{

  // todo: move this interface to /Repository/<#=
    mvcHost.ControllerRootName #>
  public interface I<#= mvcHost.ControllerRootName #>Repository
  {
  }

  // todo: move this class to /Repository/<#=
                                    mvcHost.ControllerRootName #>

  public class <#= mvcHost.ControllerRootName #>Repository :
     I<#= mvcHost.ControllerRootName #>Repository
  {
    private IMyProjectEntities _MyProjectEntities;
    private IMembershipService _MembershipService;
    public <#= mvcHost.ControllerRootName #>Repository() : this(new
      MyProjectEntities(), new AccountMembershipService())
    {
    }

    public <#= mvcHost.ControllerRootName
      #>Repository(IMyProjectEntities myProjectEntities,
                      IMembershipService accountMembershipService)
    {
      _MyProjectEntities = myProjectEntities;
      _MembershipService = accountMembershipService;
    }
  }
  [HandleError]
  [Authorize()]
  public class <#= mvcHost.ControllerName #> : Controller
  {
  ...

Door deze tekst aan te passen in de Controller template worden opeens
Repository ondersteuning ‘afgedwongen’. In ieder geval wordt het handmatig
aanpassen van de code sterk beperkt.


Let op
: Zie dat de gegeneerde Interface en Repository
class wel nog in een aparte map geplaatst moeten worden.

Ik kan in deze templates direct verwijzen naar _MyProjectEntities want
deze template is alleen toepasbaar voor gebruik binnen dit project.

Hetzelfde willen we voor de Views:

...
<asp:Content ID="Content<#= CPHCounter #>"
  ContentPlaceHolderID="<#= mvcHost.PrimaryContentPlaceHolderID #>"
  runat="server">
<script src="http://code.jquery.com/jquery-latest.js"
  type="text/javascript"></script>
<script src="<%= Url.Content("~/jQuery/confirmplugin.js") %>"
  type="text/javascript"></script>
<script type="text/javascript">
  $(document).ready(function () {
    $("form :input[type='text']:enabled:first").focus();
    $('input[type=submit]').confirm({
      msg: 'Are you sure?',
      timeout: 10000
    });
  });
</script>
<h2><#= mvcHost.ViewName #></h2>
...
<% using (Html.BeginForm()) {%>
<%= Html.ValidationSummary(true, "Creation failed. Please check
 the fields.") %>
<fieldset>
...

Met het toevoegen van bovenstaande code is opeens jQuery ondersteuning
beschikbaar om de cursor binnenin  het eerste tekst-invoerveld te plaatsen en er
zal een bevestiging gevraagd worden bij het indrukken van een Submit knop. Ook laat ik nu een ValidationSummary aanmaken want die is standaard niet
aanwezig.

Let op: De JQuery import zou ook via een MasterPage
kunnen gebeuren maar ik heb dit ter illustratie hier toegevoegd.

Het aanpassen va de Index template (List.TT)  voor THead en TBody en
Zebra strepen in een tabel is zodoende ook erg triviaal geworden:

...
<script src="htt p://code.jquery.com/jquery-latest.js"
  type="text/javascript"></script>
<script type="text/javascript" charset="utf-8">
// Laden
$(document).ready(function () {
  $('tbody tr:odd',
     this).removeClass('even').addClass('odd');
  $('tbody tr:even',
     this).removeClass('odd').addClass('even');
});
</script>
<h2><#= mvcHost.ViewName #></h2>
...
<table>
<thead>
<tr>
<th></th>
<# foreach(KeyValuePair<string, string> property in properties)
{ #>
<th>
<#= property.Key #>
</th>
<# } #>
</tr>
</thead>
<tbody>
<% foreach (var item in Model) { %>
...
<% } %>
</tbody>
</table>
...

Dus met deze simpele aanpassingen zijn de templates volledig naar onze
hand te zetten. Maar pas op! Er bestaat een risico dat we nu juist te veel in onze controllers en view gaan oplossen. Plaats geen oneigenlijke logica in deze programmaonderdelen. Je kunt beter Fat Model, skinny Controllers nastreven.

Veel succes met het genereren.

Advertenties