Как я могу вручную создать тип тела WCF OperationContract?

Я пытаюсь закодировать клиент веб-службы в Silverlight для службы RESTful WCF, которую я разработал. В Silverlight я создаю тело WebRequest, используя экземпляр DataContractSerializer.

Этот подход отлично работает, если есть единственный аргумент для OperationContract. Это не работает так хорошо, если в OperationContract определено несколько аргументов. Я считаю, что это связано с тем, что WCF создает динамический тип, который назван в честь OperationContract, а члены этого типа названы в соответствии с параметрами, определенными для операции. Цель динамического типа - гарантировать, что в теле сообщения, отправляемого в службу WCF, существует единственный элемент XML ... имеет смысл. Вопрос в том, как мне самому создать этот динамический тип, чтобы я мог сам отправить его в DataContractSerializer.

Первый пример - это рабочий пример, определяющий единственный параметр. Второй пример - это сценарий, который я пытаюсь решить (несколько параметров).


Пример 1:

[OperationContract,
WebInvoke(Method = HttpMethodType.Post,
    BodyStyle = WebMessageBodyStyle.Bare,
    UriTemplate = "UnregisterProvider"),
WebHelp(Comment = "Unregistered the provider type with the specified URI.")]
void UnregisterProvider(RdfUri providerUri);

Код, используемый для сериализации тела сообщения:

StringBuilder msgBody = new StringBuilder(250);
using (XmlWriter xw = XmlWriter.Create(msgBody))
{
    var serializer = new DataContractSerializer(typeof(RdfUri));
    serializer.WriteObject(xw, providerUri);
}

Результирующее тело:

<RdfUri xmlns="http://schemas.datacontract.org/2004/07/Intellidimension.Rdf">esp:semanticserver</RdfUri>

Пример 2:

[OperationContract,
WebInvoke(Method = HttpMethodType.Post,
    BodyStyle = WebMessageBodyStyle.WrappedRequest,  /* WrappedRequest must somehow signal WCF to create the anonymous type as it is required for multiple parameter OperationContracts */
    UriTemplate = "RegisterProvider"),
WebHelp(Comment = "Registered a provider type with the specified URI.")]
void RegisterProvider(PoolableEntityServiceProviderDescriptor descriptor, RdfUri providerUri);

Код, используемый для сериализации тела сообщения:

//?????

Результирующее тело:

<RegisterProvider xmlns="http://tempuri.org/">
  <descriptor i:type="a:SemanticServerProviderDescriptor" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:a="http://schemas.datacontract.org/2004/07/Intellidimension.RdfEntity.Service.DataContracts">
    <a:ConnectionString>Data Source=.\sqlexpress;Initial Catalog=RdfTest1;Persist Security Info=True;User ID=sa;Password=password</a:ConnectionString>
    <a:ProviderGraphUri>http://entitystore/graph-provider</a:ProviderGraphUri>
  </descriptor>
  <providerUri>esp:semanticserver</providerUri>
</RegisterProvider>

Обновление 1:

Вот парень на форумах MSDN, задающий аналогичный вопрос: Могу ли я использовать DataContractSerializerOperationFormatter для форматирования списка параметров от клиента к серверу?

DataContractSerializerOperationFormatter - это внутренний класс. Похоже, мне, возможно, придется реализовать это поведение для моего клиента.

Обновление 2:

Некоторые спрашивают, почему я не использую обычный клиент Silverlight WCF, созданный ссылкой на службу. Причина в том, что служба WCF на сервере является службой RESTful. Из документов:

Никакого аналога WebHttpBinding, предоставляемого в WCF, не предусмотрено. Чтобы получить доступ к чистым службам HTTP, REST, RSS / Atom или AJAX из Silverlight 2, используйте методы, описанные в разделе «Доступ к службам на основе HTTP напрямую», например класс WebClient. Чтобы получить доступ к службам ASP.NET AJAX, см. Доступ к службам ASP.NET AJAX.


person Eric Schoonover    schedule 12.03.2009    source источник


Ответы (2)


На самом деле я этого не пробовал, но в этой статье может быть то, что вы хотите. Он создал обернутое сообщение следующим образом:

    private void ConsumeWcfRest()
    {
        string url = "http://something/MyWebsite/tagservice";

        Tag tag1 = new Tag() { ID = 1, TagName = "test1" };
        Tag tag2 = new Tag() { ID = 2, TagName = "test2" };

        HttpWebRequest req = HttpWebRequest.Create(url + "/tags/wrapped/1") as HttpWebRequest;
        req.Method = "POST";
        string content = "<DoSomethingWrapped xmlns='http://mytagservice'>";
        content = content + DoSerialize(tag1, "tag1");
        content = content + DoSerialize(tag2, "tag2");
        content = content + "</DoSomethingWrapped>";

        // .....
    }

    private string DoSerialize(object obj, string rootName)
    {
        System.Runtime.Serialization.DataContractSerializer se;
        if (string.IsNullOrEmpty(rootName))
            se = new System.Runtime.Serialization.DataContractSerializer(obj.GetType());
        else
            se = new System.Runtime.Serialization.DataContractSerializer(obj.GetType(), rootName, "");

        System.IO.MemoryStream ms = new System.IO.MemoryStream();
        se.WriteObject(ms, obj);
        ms.Position = 0;
        byte[] arr = new byte[ms.Length];
        ms.Read(arr, 0, Convert.ToInt32(ms.Length));
        return new System.Text.UTF8Encoding().GetString(arr);
    }

Так что в основном он использует обычный DataContractSerializer для сериализации каждого аргумента, а затем вручную помещает вокруг него теги XML. Это кажется немного взломанным, но может сработать ...

Возможно, вы захотите начать декомпилировать библиотеки WCF и посмотреть, сможете ли вы узнать, как WCF делает это в первую очередь.

person CodingWithSpike    schedule 15.06.2012

Позвольте мне задать вам вопрос. Допустим, вы можете заставить это работать. Каков тип содержимого тела запроса POST?

Вы злоупотребляете HTTP, пытаясь сделать вид, что POST может отправлять две вещи. Если вы хотите делать то, что делаете, почему бы не использовать обычные контракты служб WCF. С ними легко отправить несколько параметров.

Проблема с System.ServiceModel.Web в том, что он только на 90% отделяет клиента от WCF. Поэтому, чтобы все работало правильно, вам понадобится WCF на стороне клиента. В этот момент почему бы не придерживаться обычных контрактов на службы WCF.

Если вы действительно хотите использовать HTTP по назначению, используйте технологию, которая поможет, а не мешает.

person Darrel Miller    schedule 12.03.2009
comment
Я думаю, это второй раз, когда вы говорите мне не использовать WCF :) Мне это несколько забавно. Я ценю вашу настойчивость. Тем не менее, в теле POST не публикуется несколько вещей, есть один элемент, который упаковывается и раскладывается на сервере на несколько параметров метода. - person Eric Schoonover; 12.03.2009
comment
Кроме того, я использую Silverlight и DataContractSerializer, потому что это удобно, но я не понимаю, как WCF требует от клиента наличия WCF в любом случае. Конечная точка RESTful WCF такая же, как и любая другая конечная точка RESTful. Правильно? - person Eric Schoonover; 12.03.2009
comment
Я не говорю, что невозможно создать клиента, отличного от WCF, который будет взаимодействовать со службой WCF, однако вы столкнетесь со многими проблемами. Попробуйте отправить поток в конечную точку WCF. Попробуйте выполнить базовую аутентификацию с именем пользователя, отличным от Windows. Попробуйте вернуть ошибку, отличную от стандартной html-страницы. - person Darrel Miller; 13.03.2009
comment
Может, я слишком рано начал с этого. Я использовал REST Starter Kit, когда он назывался Biztalk Services SDK в мае 2007 года. Попробуйте контролировать формат параметра даты в строке запроса. Попробуйте поддерживать URL-адреса с дополнительными сегментами. Мой список проблем был бесконечным. - person Darrel Miller; 13.03.2009
comment
Попробуйте ответы в формате XML без пространств имен. Попробуйте вернуть свой собственный тип носителя. Для меня на каждом этапе пути были трения. - person Darrel Miller; 13.03.2009
comment
Вы должны публиковать все эти проблемы как вопросы на SO :) - person Eric Schoonover; 13.03.2009