Если кто не знает, то T4MVC — это замечательная штука, которая позволяет строготипизировать в MVC3 то, что еще недостроготипизировано из коробки :)
В частности, с его помощью очень удобно генерировать ссылки на MVC-экшены в хтмл:
// в контроллере: public ActionResult Index(int a, string b) {} // во вьюшке (Razor) <a href="@Url.Action(MVC.Home.Index(10, 'some_string'))">link</a> //в браузере на клиенте <a href="/Home/Index?a=10&b=some_string">link</a>
Проблемы возникают, когда параметры экшена — динамические, и их значения можно определить только в рантайме (например, параметром является какой-нибудь javascript-атрибут).
Допустим, у нас есть список, и по клику на любом его элементе в нижележащий div загружается страничка с сервера (по ссылке вида: http://localhost/Home/Region?id={element_id}), причем эта ссылка, как видно, зависит от id элемента в списке.
Пример в псевдокоде:
<ul> <li id="153" onclick="HandleClick();">Первый элемент</li> <li id="215" onclick="HandleClick();">Второй элемент</li> </ul> <script> function HandleClick() { var id = $(this).attr("id"); $('#somediv').load("http://localhost/Home/Region?id="+id); } </script>
В примере выше все работает, но ссылка забита прямо в код, и если у нас изменится имя экшена, или имя параметра, или добавится еще один параметр в экшен, то мы об этом узнаем, только когда у кого-нибудь из клиентов что-то «свалится» :) Поэтому привлечем T4MVC и сделаем ссылку строготипизированной, тогда все ошибки мы будем иметь на стадии компиляции.
Очень хочется сделать так:
var id = $(this).attr("id"); $('#somediv').load("@Url.Action(MVC.Home.Region(id))");
но, естественно, это невозможно, потому что id — это javascript-переменная, а @Url.Action выполняется на сервере.
Неплохим решением будет использовать workaround типа:
var id = $(this).attr("id"); $('#somediv').load("@Url.Action(MVC.Home.Region(-1))".replace('-1', id));
В этом случае в яваскрипте у нас будет урл вида: «http://localhost/Home/Region?id=-1», и на уровне javascript мы заменим «-1» на требуемый нам id.
Данный прием неплох, но его минус в том, что подставляемое значение (-1 в нашем случае) должно совпадать с типом переменной, и в случае когда параметр, например, типа Guid задать значение «по умолчанию» не так просто.
Для таких случаев я написал небольшой класс-helper, который помогает получать url очень похожие на шаблоны String.Format: «http://localhost/Home/Region?id=(0)¶m=(1)»
//пример использования: @Url.ActionWithPlaceholders(MVC.Home.Region(Placeholders.Guid("(0)"), Placeholders.Guid("(1)")) //на выходе будет http://localhost/Home/Region?guid1=(0)&guid2=(1)
Суть работы в генерации псевдо-значений нужных типов (функция Placeholders.Guid), подстановке их в URL, и дальнейшей замене псевдо-значений на плейсхолдеры (Url.ActionWithPlaceholders)
P.S. Исследуя проблему, я задавал этот вопрос на stackoverflow. Любопытствующие по ссылке смогут прочитать мнение Дэвида Эббо на описанную проблему.
P.P.S.
Полный код класса для ознакомления с деталями реализации и/или копипасты (применительно к параметрам типа Guid) :)
// аналог функции @Url.Action, включающий в себя процессинг плейсхолдеров public static string ActionWithPlaceholders(this UrlHelper urlHelper, ActionResult result) { var routeValues = result.GetRouteValueDictionary(); var newRouteValues = new RouteValueDictionary(); foreach (var routeValue in routeValues) { newRouteValues[routeValue.Key] = Placeholders.GetPlaceholderFor(routeValue.Value); } Placeholders.Clear(); return urlHelper.RouteUrl(null, newRouteValues); } // класс для хранения плейсхолдеров и псевдо-значений public class Placeholders { private static readonly Dictionary<Guid, string> GuidPlaceholders = new Dictionary<Guid, string>(); public static Guid Guid(string placeholder) { var value = System.Guid.NewGuid(); while (GuidPlaceholders.ContainsKey(value)) value = System.Guid.NewGuid(); GuidPlaceholders[value] = placeholder; return value; } public static object GetPlaceholderFor(object originalValue) { var valueType = originalValue.GetType(); if (typeof(Guid) == valueType) { var typedValue = (Guid)originalValue; if (GuidPlaceholders.ContainsKey(typedValue)) { return GuidPlaceholders[typedValue]; } } return originalValue; } public static void Clear() { IntPlaceholders.Clear(); GuidPlaceholders.Clear(); } }
Неплохо, хотя и костыль.
Думаю, скобочки из Placeholders.Guid(«(0)») можно убрать
Костылек, согласен. Но проблему решил, а более красивых решений не придумалось :)
Скобочки да, можно убрать.. а можно и оставить, будет чуть понятнее при первом взгляде :)