Пишем код

Заметки о .net разработке

Строготипизированные URL в Javascript c T4MVC

2 комментария

Если кто не знает, то T4MVC — это замечательная штука, которая позволяет строготипизировать в MVC3 то, что еще недостроготипизировано из коробки :)
В частности, с его помощью очень удобно генерировать ссылки на MVC-экшены в хтмл:

<br />
// в контроллере:<br />
public ActionResult Index(int a, string b) {} </p>
<p>// во вьюшке (Razor)<br />
<a href="@Url.Action(MVC.Home.Index(10, 'some_string'))">link</a>  </p>
<p>//в браузере на клиенте<br />
<a href="/Home/Index?a=10&b=some_string">link</a><br />

Проблемы возникают, когда параметры экшена — динамические, и их значения можно определить только в рантайме (например, параметром является какой-нибудь javascript-атрибут).

Допустим, у нас есть список, и по клику на любом его элементе в нижележащий div загружается страничка с сервера (по ссылке вида: http://localhost/Home/Region?id={element_id}), причем эта ссылка, как видно, зависит от id элемента в списке.
Пример в псевдокоде:

</p>
<ul>
<li id="153" onclick="HandleClick();">Первый элемент</li>
<li id="215" onclick="HandleClick();">Второй элемент</li>
</ul>
<p><script>
function HandleClick() {
    var id = $(this).attr("id");
    $('#somediv').load("http://localhost/Home/Region?id="+id);  
}
</script><br />

В примере выше все работает, но ссылка забита прямо в код, и если у нас изменится имя экшена, или имя параметра, или добавится еще один параметр в экшен, то мы об этом узнаем, только когда у кого-нибудь из клиентов что-то «свалится» :) Поэтому привлечем T4MVC и сделаем ссылку строготипизированной, тогда все ошибки мы будем иметь на стадии компиляции.
Очень хочется сделать так:
<br />
    var id = $(this).attr("id");<br />
    $('#somediv').load("@Url.Action(MVC.Home.Region(id))");<br />

но, естественно, это невозможно, потому что id — это javascript-переменная, а @Url.Action выполняется на сервере.

Неплохим решением будет использовать workaround типа:

<br />
    var id = $(this).attr("id");<br />
    $('#somediv').load("@Url.Action(MVC.Home.Region(-1))".replace('-1', id));<br />

В этом случае в яваскрипте у нас будет урл вида: «http://localhost/Home/Region?id=-1», и на уровне javascript мы заменим «-1» на требуемый нам id.
Данный прием неплох, но его минус в том, что подставляемое значение (-1 в нашем случае) должно совпадать с типом переменной, и в случае когда параметр, например, типа Guid задать значение «по умолчанию» не так просто.

Для таких случаев я написал небольшой класс-helper, который помогает получать url очень похожие на шаблоны String.Format: «http://localhost/Home/Region?id=(0)&param=(1)»

<br />
    //пример использования:<br />
    @Url.ActionWithPlaceholders(MVC.Home.Region(Placeholders.Guid("(0)"), Placeholders.Guid("(1)"))</p>
<p>    //на выходе будет<br />
    http://localhost/Home/Region?guid1=(0)&guid2=(1)<br />

Суть работы в генерации псевдо-значений нужных типов (функция Placeholders.Guid), подстановке их в URL, и дальнейшей замене псевдо-значений на плейсхолдеры (Url.ActionWithPlaceholders)

P.S. Исследуя проблему, я задавал этот вопрос на stackoverflow. Любопытствующие по ссылке смогут прочитать мнение Дэвида Эббо на описанную проблему.

P.P.S.
Полный код класса для ознакомления с деталями реализации и/или копипасты (применительно к параметрам типа Guid) :)

<br />
    // аналог функции @Url.Action, включающий в себя процессинг плейсхолдеров<br />
    public static string ActionWithPlaceholders(this UrlHelper urlHelper, ActionResult result)<br />
    {<br />
        var routeValues = result.GetRouteValueDictionary();<br />
        var newRouteValues = new RouteValueDictionary();<br />
        foreach (var routeValue in routeValues)<br />
        {<br />
            newRouteValues[routeValue.Key] = Placeholders.GetPlaceholderFor(routeValue.Value);<br />
        }</p>
<p>        Placeholders.Clear();</p>
<p>        return urlHelper.RouteUrl(null, newRouteValues);<br />
    }</p>
<p>    // класс для хранения плейсхолдеров и псевдо-значений<br />
    public class Placeholders<br />
    {<br />
        private static readonly Dictionary<Guid, string> GuidPlaceholders = new Dictionary<Guid, string>();<br />
        public static Guid Guid(string placeholder)<br />
        {<br />
            var value = System.Guid.NewGuid();<br />
            while (GuidPlaceholders.ContainsKey(value))<br />
                value = System.Guid.NewGuid();</p>
<p>            GuidPlaceholders[value] = placeholder;<br />
            return value;<br />
        }</p>
<p>        public static object GetPlaceholderFor(object originalValue)<br />
        {<br />
            var valueType = originalValue.GetType();<br />
            if (typeof(Guid) == valueType)<br />
            {<br />
                var typedValue = (Guid)originalValue;<br />
                if (GuidPlaceholders.ContainsKey(typedValue))<br />
                {<br />
                    return GuidPlaceholders[typedValue];<br />
                }<br />
            }</p>
<p>            return originalValue;<br />
        }</p>
<p>        public static void Clear()<br />
        {<br />
            IntPlaceholders.Clear();<br />
            GuidPlaceholders.Clear();<br />
        }<br />
    }<br />

Опубликовать в Facebook
Опубликовать в Google Plus

Written by Shaddix

Октябрь 27th, 2011 at 5:08 пп

Posted in .net,MVC,t4mvc,web

2 комментария to 'Строготипизированные URL в Javascript c T4MVC'

Subscribe to comments with RSS or TrackBack to 'Строготипизированные URL в Javascript c T4MVC'.

  1. Неплохо, хотя и костыль.

    Думаю, скобочки из Placeholders.Guid(«(0)») можно убрать

    AlexIdsa

    28 Окт 11 at 13:40

  2. Костылек, согласен. Но проблему решил, а более красивых решений не придумалось :)
    Скобочки да, можно убрать.. а можно и оставить, будет чуть понятнее при первом взгляде :)

    Shaddix

    29 Окт 11 at 15:01

Leave a Reply