Как я научился выдавать файл clientaccesspolicy.xml без IIS
Недавно один разработчик обратился ко мне и Мише с вопросом.
Суть вопроса такова.
Есть silverlight клиент, есть WCF сервис. Сервис доступен, скажем, по адресу https://localhost:8731/classic с wsHttpBinding или даже basicHttpBinding.
Сервис self hosted -ну то есть без всякого IIS в консольном приложении или сервисе есть код, который запускает сервис. Как то так.
Code Snippet
ServiceHost sh = new ServiceHost(typeof(Service1));
sh.Open();
Console.WriteLine("up and running");
Console.ReadLine();
sh.Close();
Ну и есть собственно сервис, в моем примере это IService2 и класс Service1.
Code Snippet
// Classical wsHttpBinding or basicHttpBinding
[ServiceContract]
public interface IService2
{
[OperationContract]
int DoAdd(int a, int b);
}
public class Service1 : IService2
{
public int DoAdd(int a, int b)
{
return a + b;
}
\
Конфиг выглядит примерно так
Code Snippet
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<!-- When deploying the service library project, the content of the config file must be added to the host's
app.config file. System.Configuration does not support config files for libraries. -->
<system.serviceModel>
<services>
<service behaviorConfiguration="WcfServiceLibrary1.Service1Behavior"
name="WcfServiceLibrary1.Service1">
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<endpoint address="classic" binding="basicHttpBinding" contract="WcfServiceLibrary1.IService2" />
<host>
<baseAddresses>
<add baseAddress="https://localhost:8731/" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="WcfServiceLibrary1.Service1Behavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Задача – сделать так чтобы по адресу https://localhost:8731/clientaccesspolicy.xml выдавался нужный нам файл.
Решение
Воспользуемся возможностями .NET 3.5 по работе с REST.
1) Добавим контракт, который будет реализовывать выдачу файла.
Code Snippet
// This service exposes operations via REST
[ServiceContract]
public interface IService1
{
[OperationContract]
[WebGet(UriTemplate = "/clientaccesspolicy.xml")]
Stream GetClientPolicy();
}
Обратим внимание на параметр URITemplate – этот параметр описывает, как должен выглядеть WEB запрос.
2) Добавим настройки в конфиг файл. Нам нужно по адресу https://localhost:8731 добавить endpoint с webBinding, а также прописать endpointBehavior. Можно сделать из утилиты WCF Service Configuraiton Editor, можно руками.
Итак, что пришлось добавить.
Code Snippet
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service behaviorConfiguration="WcfServiceLibrary1.Service1Behavior"
name="WcfServiceLibrary1.Service1">
<endpoint address="" behaviorConfiguration="webHttp" binding="webHttpBinding"
contract="WcfServiceLibrary1.IService1">
</endpoint>
.....
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="webHttp">
<webHttp />
</behavior>
</endpointBehaviors>
....
</behaviors>
</system.serviceModel>
</configuration>
Итоговый конфиг
Code Snippet
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<!-- When deploying the service library project, the content of the config file must be added to the host's
app.config file. System.Configuration does not support config files for libraries. -->
<system.serviceModel>
<services>
<service behaviorConfiguration="WcfServiceLibrary1.Service1Behavior"
name="WcfServiceLibrary1.Service1">
<endpoint address="" behaviorConfiguration="webHttp" binding="webHttpBinding"
contract="WcfServiceLibrary1.IService1">
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<endpoint address="classic" binding="basicHttpBinding" contract="WcfServiceLibrary1.IService2" />
<host>
<baseAddresses>
<add baseAddress="https://localhost:8731/" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="webHttp">
<webHttp />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="WcfServiceLibrary1.Service1Behavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
То есть мы сказали WCF, что запросы по адресу https://localhost:8731 обрабатывает webHttpBinding, а по адресу https://localhost:8731/classic – классический binding. (я взял wsHttpBinging, вроде говорят что для silverlight нужен другой).
3) Осталось написать реализацию. Я положи нужный мне XML в ресурсы, чтобы код не загромождать.
Code Snippet
public Stream GetClientPolicy()
{
byte[] buffer = null;
using (MemoryStream ms = new MemoryStream())
{
ms.Position = 0;
using (StreamWriter sw = new StreamWriter(ms))
{
sw.WriteLine(Resource1.crossdomainpolicy1);
}
buffer = ms.GetBuffer();
}
WebOperationContext.Current.OutgoingResponse.ContentType = "text/xml";
return new MemoryStream(buffer);
}
Обратим внимание на следующие моменты.
С помощью специального класса WebOperationContext, содержащего настройки специфичные для web запроса и ответа, я настроил правильный тип содержимого.
Второе – я зачем то занимаюсь шаманством с закрытием и открытием потока. Объясняю.
Метод требует возврата ОТКРЫТОГО потока – поток закрывается сам дальше. Но у меня же есть еще и StreamWriter, который я честно пытаюсь закрыть! А он берет и закрывает нижележащий поток (MemoryStream). Подумал я , да и решил закрыть StreamWriter, MemoryStream , а потом открыть заново.
ВСЁ! Не так страшно и сложно.
Итог – у меня есть сервис, который замечательно выдает clientaccesspolicy.xml, а также работает как обычный сервис.
пример кода приложен.
Ссылки
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
Comments
- Anonymous
May 02, 2012
Опечатка в [WebGet(UriTemplate = "/clientacccesspolicy.xml")] - лишняя буква c Если не заметить, можно долго думать почему не работает.