Как я научился выдавать файл clientaccesspolicy.xml без IIS

Недавно один разработчик обратился ко мне и Мише с вопросом.

Суть вопроса такова.

Есть silverlight клиент, есть WCF сервис. Сервис доступен, скажем, по адресу https://localhost:8731/classic с wsHttpBinding или даже basicHttpBinding.

Сервис self hosted -ну то есть без всякого IIS в консольном приложении или сервисе есть код, который запускает сервис. Как то так.

Code Snippet

  1.             ServiceHost sh = new ServiceHost(typeof(Service1));

  2.             sh.Open();

  3.             Console.WriteLine("up and running");

  4.             Console.ReadLine();

  5.             sh.Close();

Ну и есть собственно сервис, в моем примере это IService2 и класс Service1.

Code Snippet

  1. // Classical wsHttpBinding or basicHttpBinding

  2. [ServiceContract]

  3. public interface IService2

  4. {

  5.     [OperationContract]

  6.     int DoAdd(int a, int b);

  7. }

  8. public class Service1 : IService2

  9. {

  10.     public int DoAdd(int a, int b)

  11.     {

  12.         return a + b;

  13.     }

  14. \

Конфиг выглядит примерно так

Code Snippet

  1. <?xml version="1.0" encoding="utf-8" ?>

  2. <configuration>

  3.   <system.web>

  4.     <compilation debug="true" />

  5.   </system.web>

  6.   <!-- When deploying the service library project, the content of the config file must be added to the host's

  7.   app.config file. System.Configuration does not support config files for libraries. -->

  8.   <system.serviceModel>

  9.     <services>

  10.       <service behaviorConfiguration="WcfServiceLibrary1.Service1Behavior"

  11.         name="WcfServiceLibrary1.Service1">

  12.         <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />

  13.         <endpoint address="classic" binding="basicHttpBinding" contract="WcfServiceLibrary1.IService2" />

  14.         <host>

  15.           <baseAddresses>

  16.             <add baseAddress="https://localhost:8731/" />

  17.           </baseAddresses>

  18.         </host>

  19.       </service>

  20.     </services>

  21.     <behaviors>

  22.       <serviceBehaviors>

  23.         <behavior name="WcfServiceLibrary1.Service1Behavior">

  24.           <serviceMetadata httpGetEnabled="true" />

  25.           <serviceDebug includeExceptionDetailInFaults="true" />

  26.         </behavior>

  27.       </serviceBehaviors>

  28.     </behaviors>

  29.   </system.serviceModel>

  30. </configuration>

Задача – сделать так чтобы по адресу https://localhost:8731/clientaccesspolicy.xml выдавался нужный нам файл.

Решение

Воспользуемся возможностями .NET 3.5 по работе с REST.

1) Добавим контракт, который будет реализовывать выдачу файла.

Code Snippet

  1.     // This service exposes operations via REST

  2.     [ServiceContract]

  3.     public interface IService1

  4.     {

  5.         [OperationContract]

  6.         [WebGet(UriTemplate = "/clientaccesspolicy.xml")]

  7.         Stream GetClientPolicy();

  8.     }

Обратим внимание на параметр URITemplate – этот параметр описывает, как должен выглядеть WEB запрос.

2) Добавим настройки в конфиг файл. Нам нужно по адресу https://localhost:8731 добавить endpoint с webBinding, а также прописать endpointBehavior. Можно сделать из утилиты WCF Service Configuraiton Editor, можно руками.

Итак, что пришлось добавить.

 

Code Snippet

  1. <?xml version="1.0" encoding="utf-8" ?>

  2. <configuration>

  3.   <system.serviceModel>

  4.     <services>

  5.       <service behaviorConfiguration="WcfServiceLibrary1.Service1Behavior"

  6.         name="WcfServiceLibrary1.Service1">

  7.         <endpoint address="" behaviorConfiguration="webHttp" binding="webHttpBinding"

  8.           contract="WcfServiceLibrary1.IService1">

  9.         </endpoint>

  10.          .....

  11.       </service>

  12.     </services>

  13.     <behaviors>

  14.       <endpointBehaviors>

  15.         <behavior name="webHttp">

  16.           <webHttp />

  17.         </behavior>

  18.       </endpointBehaviors>

  19.        ....

  20.       </behaviors>

  21.   </system.serviceModel>

  22. </configuration>

 

Итоговый конфиг

 

Code Snippet

  1. <?xml version="1.0" encoding="utf-8" ?>

  2. <configuration>

  3.   <system.web>

  4.     <compilation debug="true" />

  5.   </system.web>

  6.   <!-- When deploying the service library project, the content of the config file must be added to the host's

  7.   app.config file. System.Configuration does not support config files for libraries. -->

  8.   <system.serviceModel>

  9.     <services>

  10.       <service behaviorConfiguration="WcfServiceLibrary1.Service1Behavior"

  11.         name="WcfServiceLibrary1.Service1">

  12.         <endpoint address="" behaviorConfiguration="webHttp" binding="webHttpBinding"

  13.           contract="WcfServiceLibrary1.IService1">

  14.         </endpoint>

  15.         <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />

  16.         <endpoint address="classic" binding="basicHttpBinding" contract="WcfServiceLibrary1.IService2" />

  17.         <host>

  18.           <baseAddresses>

  19.             <add baseAddress="https://localhost:8731/" />

  20.           </baseAddresses>

  21.         </host>

  22.       </service>

  23.     </services>

  24.     <behaviors>

  25.       <endpointBehaviors>

  26.         <behavior name="webHttp">

  27.           <webHttp />

  28.         </behavior>

  29.       </endpointBehaviors>

  30.       <serviceBehaviors>

  31.         <behavior name="WcfServiceLibrary1.Service1Behavior">

  32.           <serviceMetadata httpGetEnabled="true" />

  33.           <serviceDebug includeExceptionDetailInFaults="true" />

  34.         </behavior>

  35.       </serviceBehaviors>

  36.     </behaviors>

  37.   </system.serviceModel>

  38. </configuration>

 

То есть мы сказали WCF, что запросы по адресу https://localhost:8731 обрабатывает webHttpBinding, а по адресу https://localhost:8731/classic – классический binding. (я взял wsHttpBinging, вроде говорят что для silverlight  нужен другой).

3) Осталось написать реализацию. Я положи нужный мне XML в ресурсы, чтобы код не загромождать.

Code Snippet

  1.         public Stream GetClientPolicy()

  2.         {

  3.             byte[] buffer = null;

  4.             using (MemoryStream ms = new MemoryStream())

  5.             {

  6.                 ms.Position = 0;

  7.                 using (StreamWriter sw = new StreamWriter(ms))

  8.                 {

  9.                     sw.WriteLine(Resource1.crossdomainpolicy1);

  10.                 }

  11.                 buffer = ms.GetBuffer();

  12.             }

  13.             WebOperationContext.Current.OutgoingResponse.ContentType = "text/xml";

  14.             return new MemoryStream(buffer);

  15.         }

Обратим внимание на следующие моменты.

С помощью специального класса WebOperationContext, содержащего настройки специфичные для web запроса и ответа, я настроил правильный тип содержимого.

Второе – я зачем то занимаюсь шаманством с закрытием и открытием потока. Объясняю.

Метод требует возврата ОТКРЫТОГО потока – поток закрывается сам дальше. Но у меня же есть еще и StreamWriter, который я честно пытаюсь закрыть! А он берет и закрывает нижележащий поток (MemoryStream). Подумал я , да и решил закрыть StreamWriter, MemoryStream , а потом открыть заново.

ВСЁ! Не так страшно и сложно.

Итог – у меня есть сервис, который замечательно выдает clientaccesspolicy.xml,  а также работает как обычный сервис.

image

пример кода приложен.

Ссылки

ms-help://MS.MSDNQTR.v90.en/wcf_con/html/0283955a-b4ae-458d-ad9e-6fbb6f529e3d.htm

https://msdn.microsoft.com/en-us/library/cc681221(VS.100).aspx

ClientAccessViaWCF.zip