Настройка клиента WCF и сервиса для использования с protobuf-net

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

Я хочу использовать protobuf-net для сериализации / десериализации сообщений, которыми обмениваются мой клиент WCF и сервис. Служба размещается в службе Windows. И клиент, и сервис настраиваются программно, используя настраиваемую привязку, очень похожую на wsHttpBinding, Код ссылки на службу генерируется с помощью параметра "Добавить ссылку на службу" в Visual Studio. ORM, используемый в сервисе WCF, - это EntityFramework 4, и его код генерируется с помощью EF 4.x POCO Generator. Более подробную информацию о конфигурации моего сервиса можно найти в вопросе, который я начал здесь (вот где я описал, что мой текущий сериализатор DataContractSerialzizer).

Я протестировал только protobuf-net с одной сервисной операцией, которая возвращает список пользовательских DTO. Вот операция (имейте в виду, что я только что скопировал мой код сюда, возможно, некоторые поля названы на моем родном языке, а не на английском):

    public static List<OsobaView> GetListOsobas()
    {
        Database DB = new Database(); // EF object context
        var retValue = DB.Baza.Osoba
                       .Select(x => new OsobaView
                       {
                           ID = x.ID,
                           Prezime = x.Prezime,
                           Ime = x.Ime,
                           Adresa = x.Adresa,
                           DatumRodjenja = x.DatumRodjenja,
                           JMBG = x.JMBG
                       });
        return retValue.ToList();
    }

Вот определение OsobaView учебный класс:

    [ProtoContract]
    public class OsobaView
    {
        [ProtoMember(1)]
        public int ID;
        [ProtoMember(2)]
        public string Prezime;
        [ProtoMember(3)]
        public string Ime;
        [ProtoMember(4)]
        public string Adresa;
        [ProtoMember(5)]
        public DateTime DatumRodjenja;
        [ProtoMember(6)]
        public string JMBG;
    }

Поскольку я использую "Добавить ссылку на сервис" для генерации ссылочного кода, мне пришлось использовать один из двух обходных путей, чтобы мой клиент распознал ProtoContract и члены:

  • использование общей сборки для DTO (что не является идеальным решением в моем случае, за исключением пользовательских DTO, из-за того, что я передаю сгенерированные EF POCO клиенту)
  • с помощью ProtoPartialMember подход

Я использовал оба из них, и я использовал и v1, и v2 из protobuf-net, все решения дали схожие результаты, что заставило меня поверить, что мой клиент вообще не десериализуется. Читать дальше.

Давайте рассмотрим случаи, когда я использовал ProtoPartialMember подход. Сначала я использовал v2. я люблю так, как ProtoOperationBehavior может быть использован. Вот сервисная операция, которая будет вызвана:

    [ProtoBuf.ServiceModel.ProtoBehavior]
    public List<OsobaView> GetListOsobas()
    {
        return OsobaQueries.GetListOsobas();
    }

Вот как я заменил DataContractSerializerOperationBehavior с ProtoOperationBehavior для необходимой сервисной операции на стороне клиента:

    OperationDescription op = Service.Proxy.Endpoint.Contract.Operations.Find("GetListOsobas");
    if (op != null)
    {
        DataContractSerializerOperationBehavior dcsBehavior = op.Behaviors.Find<DataContractSerializerOperationBehavior>();
        if (dcsBehavior != null)
            op.Behaviors.Remove(dcsBehavior);
        op.Behaviors.Add(new ProtoBuf.ServiceModel.ProtoOperationBehavior(op));
    }

И, конечно же, вот вышеупомянутая реализация обходного пути для DTO:

    [ProtoPartialMember(1, "ID")]
    [ProtoPartialMember(2, "Prezime")]
    [ProtoPartialMember(3, "Ime")]
    [ProtoPartialMember(4, "Adresa")]
    [ProtoPartialMember(5, "DatumRodjenja")]
    [ProtoPartialMember(6, "JMBG")]
    [ProtoContract]
    public partial class OsobaView
    {
    }

Теперь, когда я вызываю эту сервисную операцию от моего клиента, я получаю null, Но Фиддлер не согласен. В заголовке ответа четко сказано:

    Content-Length: 1301963
    Content-Type: application/soap+xml; charset=utf-8

... и в теле сообщения:

    <s:Body>
      <GetListOsobasResponse xmlns="http://tempuri.org/">
        <proto>CkMIpHES .../* REALLY LONG RESPONSE */... IyMDAxOA==</proto>
      </GetListOsobasResponse>
    </s:Body>

Тогда я подумал, давайте попробуем с v1. Что касается обслуживания, я не сильно изменился. Я просто удалил ссылку на v2.DLL и заменил ее ссылкой на v1.DLL. На стороне клиента мне пришлось удалить код, чтобы добавить ProtoOperationBehavior к моему поведению работы службы и добавил следующую строку вместо:

    Service.Proxy.Endpoint.Behaviors
        .Add(new ProtoBuf.ServiceModel.ProtoEndpointBehavior());

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

Что тут происходит?

PS Если что-то стоит, вот конфигурация WCF:

    CustomBinding customBinding = new CustomBinding();
    customBinding.CloseTimeout = TimeSpan.FromMinutes(10);
    customBinding.OpenTimeout = TimeSpan.FromMinutes(10);
    customBinding.ReceiveTimeout = TimeSpan.FromMinutes(10);
    customBinding.SendTimeout = TimeSpan.FromMinutes(10);
    HttpsTransportBindingElement httpsBindingElement = new HttpsTransportBindingElement();
    httpsBindingElement.AllowCookies = false;
    httpsBindingElement.BypassProxyOnLocal = false;
    httpsBindingElement.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
    httpsBindingElement.MaxBufferPoolSize = 20480000;
    httpsBindingElement.MaxBufferSize = 20480000;
    httpsBindingElement.MaxReceivedMessageSize = 20480000;
    httpsBindingElement.RequireClientCertificate = true;
    httpsBindingElement.UseDefaultWebProxy = true;
    TransportSecurityBindingElement transportSecurityElement = new TransportSecurityBindingElement();
    transportSecurityElement.EndpointSupportingTokenParameters.SignedEncrypted.Add(new UserNameSecurityTokenParameters());
    transportSecurityElement.EndpointSupportingTokenParameters.SetKeyDerivation(false);
    TransactionFlowBindingElement transactionFlowElement = new TransactionFlowBindingElement();
    TextMessageEncodingBindingElement textMessageEncoding = new TextMessageEncodingBindingElement();
    textMessageEncoding.MaxReadPoolSize = 20480000;
    textMessageEncoding.MaxWritePoolSize = 20480000;
    textMessageEncoding.ReaderQuotas = XmlDictionaryReaderQuotas.Max;
    ReliableSessionBindingElement reliableSessionElement = new ReliableSessionBindingElement();
    reliableSessionElement.ReliableMessagingVersion = ReliableMessagingVersion.WSReliableMessagingFebruary2005;
    customBinding.Elements.Add(transportSecurityElement);
    customBinding.Elements.Add(transactionFlowElement);
    customBinding.Elements.Add(textMessageEncoding);
    customBinding.Elements.Add(reliableSessionElement);
    customBinding.Elements.Add(httpsBindingElement);

    EndpointAddress endpoint = new EndpointAddress(new Uri(ServiceAddress));
    Service.Proxy = new BazaService.BazaClient(customBinding, endpoint);
    Service.Proxy.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindBySubjectName, CertificateSubject);
    CustomBehavior behavior = Service.Proxy.Endpoint.Behaviors.Find<CustomBehavior>();
    if (behavior == null)
    {
        Service.Proxy.Endpoint.Behaviors.Add(new CustomBehavior()); // message inspector
    }
    Service.Proxy.Endpoint.Contract.Behaviors.Add(new CyclicReferencesAwareContractBehavior(true));
    Service.Proxy.Endpoint.Behaviors.Add(new ProtoBuf.ServiceModel.ProtoEndpointBehavior());

    /* code used for protobuf-net v2

    OperationDescription op = Service.Proxy.Endpoint.Contract.Operations.Find("GetListOsobas");
    if (op != null)
    {
        DataContractSerializerOperationBehavior dcsBehavior = op.Behaviors.Find<DataContractSerializerOperationBehavior>();
        if (dcsBehavior != null)
            op.Behaviors.Remove(dcsBehavior);
        op.Behaviors.Add(new ProtoBuf.ServiceModel.ProtoOperationBehavior(op));
    } */

    Service.Proxy.ClientCredentials.UserName.UserName = LogOn.UserName;
    Service.Proxy.ClientCredentials.UserName.Password = LogOn.Password;
    Service.Proxy.Open();

РЕДАКТИРОВАТЬ

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

1 ответ

Решение

Немного сконцентрировавшись, я решил перезапустить решение с нуля. Я создал одну библиотеку классов для EDMX с ее POCO, одну для ServiceContract а также DataContracts и один для фактической реализации службы WCF. Затем я поделился этими двумя библиотеками, содержащими ServiceContract а также DataContracts и POCO с клиентом WCF и попытались снова, что дало те же результаты, что и раньше. После попытки некоторых других операций, которые не использовали protobuf-net для сериализации, оказалось, что они вели себя так же, как и первая, что привело к пустым полям (!).

Дело в том, что я ввернул свой клиент WCF .datasource файлы при рефакторинге после того, как я решил использовать технику обмена сборками. Так что это был типичный PEBKAC, он, конечно, прекрасно работает, когда все сделано правильно. Отличная работа с protobuf-net, Marc Gravell!

Другие вопросы по тегам