February 4th, 2010

glider

Самописный клиент-серверный протокол - часть 1

Есть задача. Надо написать вебпарт (виджет, который можно вставить в портал шарепоинта), который в соответствии с некоторой бизнес-логикой достает из листа (лист - хранилище данных в шарепоинте, аля таблица в SQL) набор подходящих айтемов (лист-айтем - это нечто вроде строчки в таблице SQL) и рендерит их в некотором виде. Кроме того вебпарт должен предоставлять юзеру способ редактировать и удалять эти айтемы.

***

Подумаем, как нам в этой нелегкой задаче может помочь платформа и традиции шарепоинта. Фронт-энд Шарепоинта написан на ASP.NET, стоковые вебпарты тоже написаны на ASP.NET. Читаем потсы в коммьюнити - тоже ASP.NET. Итак, гипножаба предлагает нам реализовать аспнет-контрол, который на любой клик юзера в постбэке лезет на сервер за дальнейшими инструкциями. Нихарашо.

Идеально было бы, если бы мы могли вынести всю разметку в голый HTML, а логику написать на джаваскрипте, но тут есть проблема. API шарепоинта реализован в двух ипостасях: тонкой оберткой вокруг COM-компонента и веб-сервисами в стиле SOAP. К сожалению, ни одна из этих опции не подходит для удобного использования в джаваскрипте. Выход только один - написать свой датасорс, тем более, что нам всего-то нужны CRUD операции с лист айтемами.

***

Раз коммуникации между клиентом и сервером сводятся к несложному круду, то в моске загорается лампочка, подписанная словом "REST" (подробнее про рест можно прочитать в статейке на MSDN). Кароче, будет у нас многофункциональный урл типа http://server:port/list?id/items. Будет уметь он пять несложных действий:
  1. GET - отдает список лист айтемов для листа с идентификатором id.
  2. GET для случая, когда к урлу дописывается вопросик и текст запроса - отдает отфильтрованный список йтемов.
  3. PUT - создает в листе итем, значения пропертей которого пришли в теле запроса.
  4. POST - апдейтит итем значениями пропертей из тела запроса. Ожидает, что к урлу дописан вопросик и идентификатор сабжевого итема.
  5. DELETE - удаляет итем из листа. Тоже ожидает того, что в урле запроса указан ид сабжа.

Небольшое отступление. Задача о культурной передаче запроса к датасорсу в строковом виде (для пункта 2) гораздо сложнее, чем кажется на первый взгляд. Чистый SQL не передашь, ибо боязно за инжекшн, да, и вообще, некошерно это. Неплохим солюшеном является использование Astoria, но и такой подход имеет свои косяки. Впрочем, здесь я прервусь, ибо дальнейшее обсуждение топика выходит за рамки этого поста.

Итак, у нас уже есть благородная цель, теперь вопрос в ее реализации. Об этом будет следующая часть рассказа (собственно, именно о ней я и хотел написать, а этот потс есть просто погружение в контекст). Те, кому пофигу дотнет, могут удалиться и почитать продолжение, а для остальных есть еще пару слов.

Есть искушение послушать гипножабу в лице "A Guide to Designing and Building RESTful Web Services with WCF 3.5" и заюзать WCF REST Starter Kit. Лучше не надо. Во-первых, в конфигах WCF сам чорт ногу сломит, а в нашей задаче нужно всего лишь на две копейки функциональности - несколько урлов, пару http-вербов, тривиальные форматы данных и никакой секьюрити. Во-вторых, не всякий ASP.NET сайт может без проблем захостить WCF-сервис. Например, шарепоинт, оказывается, не очень дружит с WCF, ибо шарепоинт юзает свой шибко вумный VirtualPathProvider, который не совместим с хостом WCF-а. В-третьих, переход на самописный протокол общения даст нам кучу гибкости в десериализации параметров, но об этом в следующей части.
glider

Самописный клиент-серверный протокол - часть 2

В предыдущем посте мы остановились на том, что хочется реализовать датасорс на ресте, чтобы из джаваскрипта удобно было оттуда тянуть данные. Вкратце задача состоит в том, чтобы заимплементить CRUD, но с небольшими особенностями, которые продиктованы природой датасорса (детали в предыдущем посте).

Можно много парить о том, какую выбрать схему секьюрити (я, например, просто юзаю виндовую аутентикацию и HttpContext.User.Identity, ибо юзер уже залогинен на сайт шарепоинта), о том, какой слабать формат урлов, как выдавать сообщения об ошибках, но сейчас мне интересно другое. Хочется, чтобы заработал вот такой кодярник (тела методов для краткости опущены, да и именование мне не совсем нравится, но суть не в этом):

Кодярник датасорса с CRUD-методами, которые замаплены на REST-урлы

Задача состоит в том, чтобы из урла и пар key-value, приходящих с HTTP запросом, поднять аргументы, которые будут переданы в метод. Причем десериализацию аргументов надо производить так, чтобы работало в общем случае, т.е. для любого метода с сигнатурой, похожей на одну из тех, что приведены выше. Навскидку задача кажется несложной - мэтчим параметры запроса и параметры метода по имени, пишем пачку конвертеров, например, из listId в SPList, обрабатываем специальные случаи типа "сгружать все незаюзанные параметры в словарик, если он указан в списке аргументов" и вроде бы все.

Кстати, здесь раскрывается одна из причин, по которой я в прошлом посте отказался от WCF. Ни за что в жизни ни один RPC-фреймворк не сможет в таком сценарии побить самописную штуку - либо придется аргументы делать plain-типами, а в методах их раскручивать, что ужасно неудобно, либо extensibility-кода понадобится писать больше, чем будет по размеру рукописный биндер.

Но тут есть один подводный камень. Проблема заключается в том, как для последних двух методов организовать поднятие SPListItem, ведь для него только listItemId нам недостаточно - надо еще знать id родительского листа. Аналогичная ситуация вырисовывается и для многих других объектов шарепоинта. Кароче, надо давать биндеру какие-то хинты, но завязываться на имена параметров не хочется. Этот паззл подкинул мне примерно год назад мой коллега Андрей, но вот только сейчас дошло, как его можно более-менее красиво решить.

Collapse )