Есть ли хороший способ расширить службу WCF, используя basicHttpBinding, чтобы позволить службе REST взаимодействовать с JSON? - PullRequest
5 голосов
/ 17 ноября 2011

У нас есть веб-сервис, запущенный и работающий в VS2010.

Некоторые из операционных контрактов выглядят так:

    [OperationContract]
    ITicket Login(string userName, byte[] passwordHash, string softwareVersion);

Т.е. у них есть сложные аргументы и сложные типы возвращаемых данных,или даже многократное возвращение.

Мы недавно запустили аутсорсинговый проект iPhone и позволяем им использовать этот сервис для связи с нашим сервером.Из того, что я узнал от них, я понял, что это не очень хорошая практика для общения с iPhone (например, отсутствие хороших способов использования WSDL).И поэтому я начал изучать возможность предоставления службы как службы REST, взаимодействующей с JSON.

Я добавил новую конечную точку, используя webHttpBinding, оформил контракты следующим образом:

    [OperationContract]
    [WebGet(UriTemplate = "/login?username={userName}&password={password}&softwareVersion={softwareVersion}", ResponseFormat=WebMessageFormat.Json)]
    ITicket Login(string userName, string password, string softwareVersion);

Этот метод теперь работает как задумано.

Затем я попытался украсить другой метод следующим образом:

    [OperationContract]
    [WebGet(UriTemplate = "/GetMetaData?ticket={ticket}",RequestFormat=WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
    IMetaData GetMetaData(ITicket ticket);

Когда я сейчас пытаюсь получить доступ к этому, я получаю следующую ошибку:

Ошибка сервера в приложении / Jetas5MobileService.Операция 'GetMetaData' в контракте 'IJetas5MobileService2' имеет переменную запроса с именем 'ticket' типа 'Jetas.MobileService.DataContracts.ITicket', но тип 'Jetas.MobileService.DataContracts.ITicket' не может быть преобразован с помощью QueryStringConverter.Переменные для значений запроса UriTemplate должны иметь типы, которые могут быть преобразованы с помощью QueryStringConverter.

Мне удалось построить OperationContract, который принимает в качестве аргумента только строку, а затем разбирает тонкую часть во внутреннем интерфейсе, используяDataContractJsonSerializer, но это больше похоже на уродливый хак.

Есть ли способ решить эту проблему лучше?Я начинающий, когда дело доходит до WCF и REST, поэтому не бойтесь указывать мне на какие-нибудь учебники для начинающих, которые могут быть там.Я пытался их искать, но огромное количество источников затрудняет поиск хороших.

Ответы [ 3 ]

2 голосов
/ 17 ноября 2011

Из того, что я узнал от них, я понял, что это не очень хорошая практика для общения с iPhone (например, отсутствие хороших способов использования WSDL).

Самой большой проблемой является не отсутствие хороших «инструментов», а отсутствие понимания, что такое WSDL и как работают веб-сервисы.Все эти инструменты, генерирующие сервисные заглушки для разработчиков, привели к тому, что разработчики не понимают, что скрывается за ними.Он работает для базовых сценариев, где все волшебство сделано для вас, но как только разработчики должны отслеживать любую проблему или расширять «инструмент» дополнительными функциями, у них возникают большие проблемы (и это обычно приводит к плохому решению).Честно говоря, разработка ПО не связана с базовыми сценариями.

REST ставит перед разработчиками большие проблемы, поскольку они не предоставляют никаких «волшебных» инструментов.REST касается правильного использования протокола HTTP и использует все преимущества существующей инфраструктуры HTTP.Без понимания основ HTTP-протокола вы не сможете создать хороший REST-сервис.Вот где вы должны начать.

Вот пример неправильного использования:

[OperationContract]
[WebGet(UriTemplate = "/login?username={userName}&password={password}&softwareVersion={softwareVersion}", ResponseFormat=WebMessageFormat.Json)]
ITicket Login(string userName, string password, string softwareVersion);

Login метод, очевидно, выполняет какое-то действие - я думаю, он создает тикет.Это абсолютно не подходит для GET HTTP-запроса.Это определенно должен быть запрос POST к ресурсу Login, возвращающий новое представление ITicket для каждого вызова.Зачем?Потому что запросы GET должны быть безопасными и идемпотентными.

  • Безопасный: запрос не должен вызывать побочных эффектов = он не должен вносить никаких изменений в ресурс, но в вашем случае он, скорее всего, создает новый ресурс.
  • Идемпотентность: этодля примера это не так важно, поскольку вы уже нарушили правило Safe, но это означает, что запрос к ресурсу должен повторяться.Это означает, что первый запрос с тем же именем пользователя, паролем и версией может создать новый ресурс, но когда запрос будет выполнен снова, он не должен создавать новый ресурс, а возвращать уже созданный.Это имеет больше смысла, когда ресурс сохраняется / поддерживается на сервере.

Поскольку HTTP-запрос GET инфраструктурой HTTP считается безопасным и идемпотентным, он обрабатывается по-другому.Например, запросы GET могут быть перенаправлены в кеширование и т. Д. Если запрос небезопасен и идемпотентен, он должен использовать метод POST.Поэтому правильное определение:

[OperationContract]
[WebInvoke(UriTemplate = "/login?username={userName}&password={password}&softwareVersion={softwareVersion}", ResponseFormat=WebMessageFormat.Json)]
ITicket Login(string userName, string password, string softwareVersion);

, потому что WebInvoke по умолчанию использует метод POST.Это также причина, по которой все туннелирование протоколов (например, SOAP) обычно используют HTTP-методы POST для всех запросов.

Другой проблемой в предыдущем примере может быть снова подход REST = полное использование инфраструктуры HTTP.Он должен использовать HTTP-аутентификацию (логин) = Basic, Digest, OAuth и т. Д. Это не означает, что у вас не может быть аналогичного ресурса, но вы должны сначала рассмотреть возможность использования стандартного способа HTTP.

Ваш второй пример на самом деленамного лучше, но у него есть проблемы с ограничением WCF.WCF может читать из URL только основные типы (кстати, как вы хотите передать объект в URL?).Любой другой тип параметра требует специального поведения WCF.Если вам нужно предоставить метод, который принимает контракт данных, вы должны снова использовать метод HTTP, который принимает параметры в теле - снова используйте POST и поместите сериализованный билет JSON в тело запроса:

[OperationContract]
[WebInvoke(UriTemplate = "/GetMetaData",RequestFormat=WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]
IMetaData GetMetaData(ITicket ticket);
2 голосов
/ 17 ноября 2011

Я столкнулся с подобной проблемой при использовании WCF Rest Starter Kit.

Если я правильно помню, переменные UriTemplate в пути всегда разрешаются в строки при использовании WebGet или WebInvoke. Переменные UriTemplate можно привязать только к int, long и т. Д., Когда они находятся в части запроса UriTemplate. То есть нет способа передать сложный объект в .

Я думаю, что нет чистого способа сделать это. Я просто использовал решение для разбора, как и вы.

Теперь вы можете проверить новый стек для выполнения REST с WCF с именем WCF Web Api . Он очень хорошо работает со сложными типами в качестве параметров метода.

1 голос
/ 17 ноября 2011

Вы должны опубликовать данные JSON в методе и можете настроить объявление следующим образом:

[OperationContract]
    [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json,
        UriTemplate = "/login", BodyStyle = WebMessageBodyStyle.Wrapped)]
    ITicket Login(string userName, string password, string softwareVersion);

Затем добавьте новую конечную точку в вашу конфигурацию следующим образом (оставив ваши существующие конечные точки и конфигурацию в том виде, как она есть, выпросто добавляем новую конечную точку JSON и новое поведение):

    <service behaviorConfiguration="Your.ServiceBehavior.Here" name="Your.Stuff.Here">
        <endpoint address="" binding="basicHttpBinding" bindingConfiguration="basicBindingSettings" behaviorConfiguration="basic" contract="Your.Contract.Here">
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
        <endpoint address="json" binding="webHttpBinding" contract="Your.Contract.Here" behaviorConfiguration="web"></endpoint>
      </service>
<behaviors>
      <endpointBehaviors>
        <behavior name="web">
          <webHttp />
        </behavior>
    </behaviors>

Затем вы можете опубликовать что-то вроде "{" userName ":" testuser "," password ":" testpass "," softwareVersion ":"1.0.0"} "на URL https://yourdomain.com/service.svc/json/login.

Если вы хотите передать сложный тип, вам просто нужно передать JSON, который соответствует пользовательскому объекту.Поэтому, если бы у вас был объект животного со свойствами цвета и размера, JSON выглядел бы как "{" animal ": {" Color ":" red "," Size ":" Large "}}".

ЭтоСледует сделать это, и вам не нужно менять реализацию метода вообще.WCF будет просто возвращать данные в формате JSON при вызове вышеописанным способом для конечной точки JSON.Ваши существующие методы SOAP будут работать в обычном режиме.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...