Gdy HtmlHelper to za mało

Czytając ostatnio o rozszerzeniach do klasy HtmlHelper, stwierdziłem że w wraz z rozwojem projektu ich różnorodność może stać się trudna do ogarnięcia i przydałoby się je pogrupować, zorganizować.
Zacząłem się więc zastanawiać jak takie rozwiązanie mogłoby wyglądać. Zaznaczam przy tym że sam tego jeszcze nie doświadczyłem, a koncepcje dalej przedstawione są raczej propozycjami niż wskazówkami.

Pierwszym narzucającym się rozwiązaniem jest konwencja nazewnicza, najlepiej przedrostkowa. Aby uniknąć zbyt długiego komentarza po prostu wstawię fragment kodu tworzonego w ten sposób.

@using(Html.BootstrapGridRow())
{
@using(Html.BootstrapGridCol(4))
{
<!-- zawartość kolumny -->
}
@using(Html.BootstrapGridCol(8))
{
<!-- zawartość drugiej kolumny -->
}
}

Kod powyższych Helperów nie różni się niczym od kodu Helperów jakie tworzy każdy kto chociaż chwilę pracował w MVC.

Jest to rozwiązanie które znacząco ułatwia szukanie interesującego Helpera, zwłaszcza po usprawnieniach w Intellisense obecnych w Visual Studio od wersji 2010 i zwłaszcza przez osobę która kilka tygodni spędziła już w zespole. Rozwiązanie to nie chroni jednak przed literówkami lub łamaniem konwencji więc zacząłem się zastanawiać czy nie da się tego zrobić lepiej.

Drugim pomysłem jest stworzenie helperów które zamiast IDisposable i HtmlString zwracają klasę która zawiera takie metody. Użycie takiego API wyglądałoby następująco:

@using(Html.Bootstrap().GridRow())
{
@using(Html.Bootstrap().GridCol(4))
{
<!-- zawartość kolumny -->
}
@using(Html.Bootstrap().GridCol(8))
{
<!-- zawartość drugiej kolumny -->
}
}

W tym podejściu na start w Intellisense widzimy tylko Helpery tworzące Grupy zamiast widzieć wszystkie metody. Wadą tego rozwiązania jest trochę mniej przejrzysty kod samych Helperów.

public static class BootstrapHelper
{
public static GroupingClass Bootstrap(this HtmlHelper self)
{
return new GroupingClass(self);
}
public class GroupingClass
{
private HtmlHelper helper;
public GroupingClass(HtmlHelper helper)
{
this.helper = helper;
}
public IDisposable GridRow()
{
// TODO: zwrócić co trzeba
}
public IDisposable GridCol(int width)
{
// TODO: Zrobić co trzeba
}
}
}

Oczywiście nazwa klasy GroupingClass została wymyślona na szybko ze względu na brak lepszego pomysłu

Na koniec stwierdziłem że ten Html na początku i te nawiasy są niepotrzebnymi ozdobnikami i najfajniej by było jakbym mógł napisać po prostu:

@using(Bootstrap.GridRow())
{
@using(Bootstrap.GridCol(4))
{
<!-- zawartość kolumny -->
}
@using(Bootstrap.GridCol(8))
{
<!-- zawartość drugiej kolumny -->
}
}

Okazuje się że jest to możliwe wymaga jednak trochę więcej gimnastyki. I wiedzy jak działa Razor. Okazuje się że działa on bardzo podobnie do silnika wykorzystywanego w WebForms. Każdy widok jest kompilowany podczas działania aplikacji do klasy dziedziczącej po System.Web.Mvc.WebViewPage – dla nietypowanych widoków, albo System.Web.Mvc.WebViewPage<TView> dla widoków typowanych. Właściwości Html, Ajax itp. pochodzą z tych właśnie klas. Klasą bazową dla kompilowanych w locie klas może być jednak inna klasa, którą można ustawić w sekcji configuration – system.web.webPages.razor – page w atrybucie pageBaseType w Web.configu, jednak nie tym Web.configu w głównym folderze aplikacji, a tym w katalogu Views.
Klasa ta powinna występować w dwóch formach (generycznej i nie) i dziedziczyć po WebViewPage. Klasa powinna być abstrakcyjna (implementację metody Execute zostawiamy widokowi).

public abstract class CustomWebViewPage : WebViewPage
{
protected CustomWebViewPage()
{
Bootstrap = new BootstrapHelper(ViewContext);
}
public BootstrapHelper Bootstrap { get; private set; }
}
public abstract class CustomWebViewPage : WebViewPage
{
protected CustomWebViewPage()
{
Bootstrap = new BootstrapHelper(ViewContext);
}
public BootstrapHelper Bootstrap { get; private set; }
}
public class BootstrapHelper
{
private ViewContext viewContext;
public BootstrapHelper(ViewContext viewContext)
{
this.viewContext = viewContext;
}
public IDisposable GridRow()
{
// TODO: zwrócić co trzeba
}
public IDisposable GridCol(int width)
{
// TODO: Zrobić co trzeba
} 
}