HTMLowe szablony dla inputów w ASP.NET MVC
Dzisiaj mam dla Was coś co przyszło mi do głowy w trakcie pracy, a mianowicie pomysł (doprawiony kodem oczywiście) na mały system szablonów do HTMLowych formularzy w ASP.NET MVC. Całość jest prosta do bólu jednak przy sprawnym wykorzystaniu może pozwolić na tworzenie np. systemu skórek.
Kiedy piszemy formularze w naszej aplikacji ASP.NET MVC to zazwyczaj albo tworzymy w pliku .cshtml tego typu konstrukcje
<tr> <td>@Html.DisplayNameFor(model => model.Name)</td> <td>@Html.TextBoxFor(model => model.Name)</td> </tr>
Albo piszemy helpery, które taki kod będą dla wszystkich inputów wstawiały za nas.
Rozwiązanie powszechnie używane jednak ma jedną wadę: w pierwszym przypadku jakakolwiek zmiana np. kolejności kolumn w tabeli powoduje konieczność wykonania sporej pracy, zaś drugi sposób oznacza konieczność pisania helperów dla każdego przypadku wynikowego html’a, przykładowo kiedy raz wyświetlany label + input w tabeli, a raz w divie musimy mieć dwie metody, które te dwa przypadki obsłużą.
W tym momencie mam do zaproponowania takie małe ulepszenie do helpera. Myślę, że o podobnym sposobie większość osób prędzej czy później by pomyślała, jednak warto podzielić się tym co mam :)
Koncepcja jest taka, żeby zamiast wpisywania HTMLowej struktury bezpośrednio w metodzie lub w pliku .cshtml można było ją przekazywać jako tekst z dowolnego źródła – zmiennej, pliku, bazy danych itd. np. w takiej postaci:
<tr> <td class='col-md-4'> {label} </td> <td class='col-md-8'> {input} </td> </tr>
Widzicie już o co chodzi? Dokładnie tak, najzwyczajniej w świecie chodzi o to, że mamy szablon, który posiada odpowiednio oznaczone miejsca gdzie umieszczone będą poszczególne elementy.
Ja przyjąłem, że dostępne atrybuty (czyli w tym wypadku „{label}” i „{input}”) będę miał zapisane jako statyczne stringi:
namespace FormTemplates { class TemplateAttributes { public static readonly string LabelAttribute = "{label}"; public static readonly string InputAttribute = "{input}"; } }
Dzięki czemu w razie potrzeby wszystko jest możliwe do zmodyfikowania w jednym miejscu.
Pora teraz na główną klasę, która jest naszym helperem:
using System; using System.Linq.Expressions; using System.Web.Mvc; using System.Web.Mvc.Html; namespace FormTemplates { public static class InputTemplateHelper { public static MvcHtmlString InputTemplateFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, MvcHtmlString input, string template) { var output = template; var label = helper.LabelFor(expression).ToHtmlString(); var htmlInput = input.ToHtmlString(); InsertLabel(label, ref output); InsertInput(htmlInput, ref output); return new MvcHtmlString(output); } private static void InsertLabel(string label, ref string template) { if (!string.IsNullOrEmpty(template) && !string.IsNullOrEmpty(label) && template.Contains(TemplateAttributes.LabelAttribute)) { template = template.Replace(TemplateAttributes.LabelAttribute, label); } } private static void InsertInput(string input, ref string template) { if (!string.IsNullOrEmpty(template) && !string.IsNullOrEmpty(input) && template.Contains(TemplateAttributes.InputAttribute)) { template = template.Replace(TemplateAttributes.InputAttribute, input); } } } }
Publiczna metoda InputTemplateFor przyjmuje jako parametr wyrażenie, dla którego chcemy użyć szablon, gotowy input w postaci stringa (zaraz zobaczycie dlaczego jest tutaj przekazywany) i na koniec szablon, jaki chcemy użyć. Wewnątrz nie dzieje się żadne rocket science bo następuje zwykłe podmienienie atrybutów szablonu na konkretny tekst lub pole.
Po co jednak input jest przekazywany jako parametr? Odpowiedź jest prosta – żeby można było wykorzystać go do różnych typów kontrolek. Przykładowo dla pól tekstowych możemy dopisać taki helper:
using System; using System.Linq.Expressions; using System.Web.Mvc; using System.Web.Mvc.Html; namespace FormTemplates { public static class TextBoxTemplateHelper { public static MvcHtmlString TextBoxTemplateFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, string template) { var input = helper.TextBoxFor(expression); return helper.InputTemplateFor(expression, input, template); } } }
I wstawiać do szablonu w pole input textbox. Samego użycia już nie będę pokazywał, myślę, że jeśli ktoś ma zamiar bawić się w takie rzeczy to sprawę wykorzystania helperów w ASP.NET MVC ma przynajmniej w podstawowym stopniu opanowaną.
I to by było na tyle. Jak wspomniałem na początku nie miałem zamiaru pokazywać tutaj wielkiej magii, a jedynie podzielić się, moim zdaniem, fajnym i dającym pewne podstawy do dalszego działania pomysłem. Jednym z większych usprawnień jakie można by tu zastosować jest wprowadzenie atrybutu dla pól, które chcemy wyświetlać określającego jaki tym inputu ma być dla niego utworzony dzięki czemu pobylibyśmy się konieczności pisania helperów dla każdego rodzaju inputu osobno. Zachęcam do eksperymentowania i modyfikowania kodu ;)
A w czym to jest lepsze od użycia zwykłych EditorTemplate i atrybutu DataType?
Prawdopodobnie w niczym, ewentualnie naciągając można powiedzieć, że ten kod da się całkowicie zmodyfikować pod własne potrzeby, ale to naciągana teoria.
Szczerze mówiąc do tej pory nie wiedziałem o EditorTemplate i DataType przez co takie cuda przyszły mi do głowy, dzięki somekind za uświadomienie, że coś takiego istnieje, człowiek przynajmniej cały czas coś nowego poznaje ;)