Здравствуйте.
Многим наверняка приходилось в своей жизни проектировать и разрабатывать RESTful API. С релизом технологии Web API делать это стало гораздо проще, а с выходом Web API 2 еще и куда приятней. Система раутинга, перекочевавшая из ASP.NET MVC, отлично справляется со своей задачей, и позволяет нам не только свободно конструировать пути, но и приправлять их различными параметрами, указывая оные в фигурных скобках. Вряд ли шаблон вида «api/{controller}/{id}» вызывает нынче у кого-то благоговейный ужас. Однако что произойдет, если какой-то из методов нашего API в качестве этого самого {id} будет принимать не число в строковом представлении, не Guid, а, скажем, адрес электронной почты? Ну, например, чтобы проверить наличие этого адреса в базе данных. Работать тогда ничего не будет, а виной всему маленькая и, казалось бы, совсем безобидная точка. Как с этим жить дальше и рассказывается под катом.
Сразу стоит оговориться, что само существование проблемы не то, чтобы надуманное, но, скажем так, легко обходимое двумя вполне стандартными путями:
— не надо выделываться, email можно передавать в модели;
— не надо выделываться, email можно передавать как параметр строки запроса.
С другой стороны, когда электронная почта является именно что идентификатором, вроде как логически и идеологически вернее помещать ее именно в путь, а не в модель или атрибут. Впрочем, спорить и дискутировать на эту тему, я уверен, можно долго, перед нами же стоит вполне конкретная проблема: наличие точки в email'е приводит к 404-й ошибке. Первопричина, думаю, вполне прозрачна: веб-сервер пытается искать файл (точка же), а файла такого, разумеется, нет.
Как же с этим можно бороться? Способов много, все они разные, вероятно даже не все возможные появятся ниже (надеюсь, комментарии нам в помощь).
Способ 1.
Слэш. Если мы добавим его в конец пути после нашего праметра {id}, то все внезапно заработает. Все потому, что пониматься этот сегмент будет не как имя файла, а как имя папки. Решение очень простое, но не очень, согласитесь, красивое, ведь придется информировать потребителя сервиса, что именно для данного метода слэш в конце ставить надо, а для других — вовсе необязательно (да их обычно никто и не ставит). Здесь же стоит упомянуть еще один интересный момент, связанный с папками и точками. Если мы поставим точку в конце сегмента прямо перед слэшем, то тоже получим 404. Не могут, дескать, имена папок в пост-MSDOS системах (а ноги растут именно оттуда) оканчиваться точкой. По этой же причине в пути нелья использовать следующие литералы: COM1-9, LPT1-9, AUX, PRT, NUL, CON. Есть, конечно, способ обойти и это, в частности ипользовав атрибут
Способ 2.
В разделе system.webServer файла конфигурации прописы��аем
и вуаля, все работает. Плохой способ, очень плохой, хотя многие сходу советуют именно его. Когда-то без этой опции действительно было просто не обойтись, но по большому счету она означает, что вместо нативных модулей для получения статического контента, такого как изображения, CSS, скрипты и т.п., каждый запрос будет проходить через весь набор управляемых модулей вашего приложения, что чревато значительными потерями производительности, как минимум. Именно поэтому подобное решение — это своеобразный такой «брутфорс».
Способ 3.
Желаемого эффекта мы можем добиться и следующей настройкой все в том же файле конфигурации:
Здесь суть заключается в том, что мы очищаем атрибут preCondition у UrlRoutingModule, разрешая таким образом данному управляемому модулю обрабатывать все входящие запросы. Способ очень похож на предыдущий с той лишь разницей, что там каждый запрос обрабатывали все управлямые модули, а здесь только один. Хрен, в общем-то, редьки не слаще.
Способ 4.
Еще в далеком 2010-м году Microsoft выпустила патч для IIS 7.0 и 7.5, который позволил хэндлерам с атрибутом path="*." понимать не только пути, оканчивающиеся точкой, но и пути без оной. Так наступила эра т.н. extensionless URLs, а наш способ номер два потерял актуальность. Описание патча, кстати говоря, можно найти здесь. Нынче же �� своем web.config'е запросто можно обнаружить подобную запись в разделе handlers:
Как это решает нашу проблему? Да в общем-то никак. Разве что значение атрибута path мы поменяем на сиротливую звездочку. Тоже своего рода выход, и наш email даже заработает, правда ни одного файла с расширением мы больше не достанем, т.к. будем получать справедливую 500-ю ошибку от этого же самого хэндлера.
Возникает вопрос: так что же делать? Ответ на него отднозначным быть, наверное, не может. Лучшим решением на мой сугубо личный взгляд здесь является использование последнего способа, но только частично. Скажем, ��сли мы знаем, что все пути к методам (и только методам, а не статическим ресурсам!) API начинаются с соответствующего префикса («api/{controller}/{id}»), то мы можем добавить еще один хэндлер того же типа именно для адресов вида «api/*»:
Таким образом мы, что называется, отделяем мух от котлет на уровне проектирования API.
P.S. Если у кого есть другие способы решения подобной проблемы, равно как и дополнительные сведения и разъяснения, то в комментариях всячески приветствуется высказывание своих и не только своих умных мыслей. Спасибо за внимание.
Многим наверняка приходилось в своей жизни проектировать и разрабатывать RESTful API. С релизом технологии Web API делать это стало гораздо проще, а с выходом Web API 2 еще и куда приятней. Система раутинга, перекочевавшая из ASP.NET MVC, отлично справляется со своей задачей, и позволяет нам не только свободно конструировать пути, но и приправлять их различными параметрами, указывая оные в фигурных скобках. Вряд ли шаблон вида «api/{controller}/{id}» вызывает нынче у кого-то благоговейный ужас. Однако что произойдет, если какой-то из методов нашего API в качестве этого самого {id} будет принимать не число в строковом представлении, не Guid, а, скажем, адрес электронной почты? Ну, например, чтобы проверить наличие этого адреса в базе данных. Работать тогда ничего не будет, а виной всему маленькая и, казалось бы, совсем безобидная точка. Как с этим жить дальше и рассказывается под катом.
Сразу стоит оговориться, что само существование проблемы не то, чтобы надуманное, но, скажем так, легко обходимое двумя вполне стандартными путями:
— не надо выделываться, email можно передавать в модели;
— не надо выделываться, email можно передавать как параметр строки запроса.
С другой стороны, когда электронная почта является именно что идентификатором, вроде как логически и идеологически вернее помещать ее именно в путь, а не в модель или атрибут. Впрочем, спорить и дискутировать на эту тему, я уверен, можно долго, перед нами же стоит вполне конкретная проблема: наличие точки в email'е приводит к 404-й ошибке. Первопричина, думаю, вполне прозрачна: веб-сервер пытается искать файл (точка же), а файла такого, разумеется, нет.
Как же с этим можно бороться? Способов много, все они разные, вероятно даже не все возможные появятся ниже (надеюсь, комментарии нам в помощь).
Способ 1.
Слэш. Если мы добавим его в конец пути после нашего праметра {id}, то все внезапно заработает. Все потому, что пониматься этот сегмент будет не как имя файла, а как имя папки. Решение очень простое, но не очень, согласитесь, красивое, ведь придется информировать потребителя сервиса, что именно для данного метода слэш в конце ставить надо, а для других — вовсе необязательно (да их обычно никто и не ставит). Здесь же стоит упомянуть еще один интересный момент, связанный с папками и точками. Если мы поставим точку в конце сегмента прямо перед слэшем, то тоже получим 404. Не могут, дескать, имена папок в пост-MSDOS системах (а ноги растут именно оттуда) оканчиваться точкой. По этой же причине в пути нелья использовать следующие литералы: COM1-9, LPT1-9, AUX, PRT, NUL, CON. Есть, конечно, способ обойти и это, в частности ипользовав атрибут
<httpRuntime relaxedUrlToFileSystemMapping="true" />
Способ 2.
В разделе system.webServer файла конфигурации прописы��аем
<modules runAllManagedModulesForAllRequests="true" />
и вуаля, все работает. Плохой способ, очень плохой, хотя многие сходу советуют именно его. Когда-то без этой опции действительно было просто не обойтись, но по большому счету она означает, что вместо нативных модулей для получения статического контента, такого как изображения, CSS, скрипты и т.п., каждый запрос будет проходить через весь набор управляемых модулей вашего приложения, что чревато значительными потерями производительности, как минимум. Именно поэтому подобное решение — это своеобразный такой «брутфорс».
Способ 3.
Желаемого эффекта мы можем добиться и следующей настройкой все в том же файле конфигурации:
<modules>
<remove name="UrlRoutingModule-4.0" />
<add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" preCondition="" />
</modules>
Здесь суть заключается в том, что мы очищаем атрибут preCondition у UrlRoutingModule, разрешая таким образом данному управляемому модулю обрабатывать все входящие запросы. Способ очень похож на предыдущий с той лишь разницей, что там каждый запрос обрабатывали все управлямые модули, а здесь только один. Хрен, в общем-то, редьки не слаще.
Способ 4.
Еще в далеком 2010-м году Microsoft выпустила патч для IIS 7.0 и 7.5, который позволил хэндлерам с атрибутом path="*." понимать не только пути, оканчивающиеся точкой, но и пути без оной. Так наступила эра т.н. extensionless URLs, а наш способ номер два потерял актуальность. Описание патча, кстати говоря, можно найти здесь. Нынче же �� своем web.config'е запросто можно обнаружить подобную запись в разделе handlers:
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
Как это решает нашу проблему? Да в общем-то никак. Разве что значение атрибута path мы поменяем на сиротливую звездочку. Тоже своего рода выход, и наш email даже заработает, правда ни одного файла с расширением мы больше не достанем, т.к. будем получать справедливую 500-ю ошибку от этого же самого хэндлера.
Возникает вопрос: так что же делать? Ответ на него отднозначным быть, наверное, не может. Лучшим решением на мой сугубо личный взгляд здесь является использование последнего способа, но только частично. Скажем, ��сли мы знаем, что все пути к методам (и только методам, а не статическим ресурсам!) API начинаются с соответствующего префикса («api/{controller}/{id}»), то мы можем добавить еще один хэндлер того же типа именно для адресов вида «api/*»:
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<add name="API-ExtensionlessUrlHandler-Integrated-4.0" path="api/*" verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
Таким образом мы, что называется, отделяем мух от котлет на уровне проектирования API.
P.S. Если у кого есть другие способы решения подобной проблемы, равно как и дополнительные сведения и разъяснения, то в комментариях всячески приветствуется высказывание своих и не только своих умных мыслей. Спасибо за внимание.
