配置 WCF 客户端和服务以与 protobuf-net 一起使用 [英] Configuring WCF client and service for use with protobuf-net

查看:43
本文介绍了配置 WCF 客户端和服务以与 protobuf-net 一起使用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我决定就这个问题提出一个新问题,可能会扩展这个问题,没有在互联网上的任何地方找到有关该问题的准确答案.

我想使用 protobuf-net 来序列化/反序列化我的 WCF 客户端和服务之间交换的消息.该服务自托管在 Windows 服务中.客户端和服务都以编程方式配置,使用与 wsHttpBinding 非常相似的自定义绑定.服务引用代码是使用 Visual Studio 中的添加服务引用"选项生成的.WCF 服务上使用的 ORM 是 EntityFramework 4,它的代码是使用 EF 4.x POCO Generator 生成的.关于我的服务配置的更多信息可以在我开始的问题中找到这里 (那是我描述我当前序列化器是 DataContractSerialzizer 的地方).

我只用一个返回自定义 DTO 列表的服务操作测试了 protobuf-net.这是操作(请注意,我只是将我的代码复制粘贴到这里,可能有一些字段是用我的母语而不是英语命名的):

 public static ListGetListOsobas(){数据库 DB = 新数据库 ();//EF 对象上下文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});返回 retValue.ToList();}

这里是OsobaView类的定义:

 [ProtoContract]公开课 OsobaView{[ProtoMember(1)]公共整数 ID;[ProtoMember(2)]公共字符串 Prezime;[ProtoMember(3)]公共字符串 Ime;[ProtoMember(4)]公共字符串 Adresa;[ProtoMember(5)]public DateTime DatumRodjenja;[ProtoMember(6)]公共字符串 JMBG;}

当我使用添加服务引用"来生成引用代码时,我必须使用两种变通方法之一才能让我的客户识别 ProtoContract 和成员:

  • 对 DTO 使用共享程序集(在我的情况下,这不是理想的解决方案,除了自定义 DTO,因为我将 EF 生成的 POCO 传递给客户端)
  • 使用 ProtoPartialMember 方法

我同时使用了它们,并且使用了 protobuf-netv1v2,所有解决方案都产生了相似的结果,这使我相信我的客户根本没有反序列化.继续阅读.

让我们考虑使用 ProtoPartialMember 方法的情况.起初我使用v2.我喜欢 ProtoOperationBehavior 的使用方式.这是要调用的服务操作:

 [ProtoBuf.ServiceModel.ProtoBehavior]公共列表GetListOsobas(){返回 OsobaQueries.GetListOsobas();}

以下是我如何将 DataContractSerializerOperationBehavior 替换为 ProtoOperationBehavior在客户端:

 OperationDescription op = Service.Proxy.Endpoint.Contract.Operations.Find("GetListOsobas");如果(操作!= null){DataContractSerializerOperationBehavior dcsBehavior = op.Behaviors.Find();如果(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")][原合同]公共部分类 OsobaView{}

现在,当我从客户端调用此服务操作时,得到 null.但Fiddler 不同意.它清楚地表明,在响应标头中:

 内容长度:1301963内容类型:应用程序/soap+xml;字符集=utf-8

...在邮件正文中:

 <GetListOsobasResponse xmlns="http://tempuri.org/"><proto>CkMIpHES .../* 响应时间很长 */... IyMDAxOA==</proto></GetListOsobasResponse></s:身体>

然后我想,让我们试试 v1.在服务方面,我没有太大变化.我刚刚删除了对 v2 .DLL 的引用,并将其替换为对 v1 .DLL 的引用.在客户端,我不得不删除将 ProtoOperationBehavior 添加到我的服务操作行为的代码,并添加了以下行:

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

我启动它,调用操作,这次结果不是null.这次是空白字段列表.再说一次,Fiddler 不能同意,因为它再次说了与之前所说的相同.相同的内容长度和相同的消息体.

这是怎么回事?

附言如果值得的话,这里是 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;ReliableSessionBindingElementreliableSessionElement = 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 行为 = Service.Proxy.Endpoint.Behaviors.Find();如果(行为==空){Service.Proxy.Endpoint.Behaviors.Add(new CustomBehavior());//消息检查器}Service.Proxy.Endpoint.Contract.Behaviors.Add(new CyclicReferencesAwareContractBehavior(true));Service.Proxy.Endpoint.Behaviors.Add(new ProtoBuf.ServiceModel.ProtoEndpointBehavior());/* 用于protobuf-net v2的代码OperationDescription op = Service.Proxy.Endpoint.Contract.Operations.Find("GetListOsobas");如果(操作!= null){DataContractSerializerOperationBehavior dcsBehavior = op.Behaviors.Find();如果(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 生成的服务引用并创建了自己的服务引用,共享整个服务合同,但没有任何变化.

解决方案

集中注意力一点后,我决定从头开始重新启动解决方案.我为带有 POCO 的 EDMX 创建了一个类库,一个用于 ServiceContractDataContracts,一个用于实际的 WCF 服务实现.然后我将这两个包含 ServiceContractDataContract 的库以及 POCO 与 WCF 客户端共享并再次尝试,结果与之前相同.在尝试了其他一些不使用 protobuf-net 进行序列化的操作后,结果发现它们的行为与第一个相同,导致空字段(!).

问题是,在我决定使用程序集共享技术后,我在重构时搞砸了我的 WCF 客户端的 .datasource 文件.所以这是一个典型的 PEBKAC,如果做得好,它当然可以正常工作.与 protobuf-net 合作出色,Marc Gravell

I decided to open a new question about this matter, maybe expanding this question, not having found a precise answer about the issue anywhere on the Internet.

I want to use protobuf-net to serialize/deserialize messages exchanged between my WCF client and service. The service is self-hosted in a Windows Service. Both client and service are configured programmatically, using a custom binding very similar to wsHttpBinding. Service reference code is generated using "Add Service Reference" option in Visual Studio. The ORM used on the WCF service is EntityFramework 4 and it's code is generated using EF 4.x POCO Generator. More info about my service configuration can be found in a question I started here (that's where I described that my current serializer is DataContractSerialzizer).

I have only tested protobuf-net with one service operation which returns a list of custom DTOs. Here is the operation (be advised that I just did a copy-paste of my code to here, there might be some fields named in my domestic language, not English):

    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();
    }

Here is the definition of OsobaView class:

    [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;
    }

As I am using "Add Service Reference" to generate the reference code, I had to use one of the two work-arounds in order to have my client recognize ProtoContracts and members:

  • using a shared assembly for DTOs (which is not an ideal solution in my case except for custom DTOs, due to the fact that I pass EF-generated POCOs to the client)
  • using ProtoPartialMember approach

I used both of them and I used both v1 and v2 of protobuf-net, all solutions yielded similar results which led me to believe my client is not deserializing at all. Read on.

Let's consider cases where I used the ProtoPartialMember approach. At first I used v2. I love the way ProtoOperationBehavior can be used. Here is the service operation to be invoked:

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

Here is how I replaced DataContractSerializerOperationBehavior with ProtoOperationBehavior for the needed service operation on client side:

    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));
    }

And of course, here is the above mentioned work-around implementation for DTO:

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

Now when I call this service operation from my client, I get null. But Fiddler disagrees. It clearly says, in response header:

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

...and in the message body:

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

Then I thought, let's try with v1. On the service side, I haven't changed much. I just removed the reference to v2 .DLL and replaced it with a reference to v1 .DLL. On the client side, I had to remove the code to add ProtoOperationBehavior to my service operation behaviors and added the following line instead:

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

I fired it up, invoked the operation, and this time the result is not null. This time it is a list of blank fields. Again, Fiddler couldn't agree because it again said the same what it said before. The same content length and the same message body.

What's going on here?

P.S. If it's worth anything, here is the WCF configuration:

    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();

EDIT

To provide even more information, I have read what's written there but it didn't help. I have deleted the service reference generated by Visual Studio and created my own, sharing the whole service contract, but nothing has changed.

解决方案

After concentrating a bit better, I decided to restart the solution from scratch. I created one class library for the EDMX with it's POCOs, one for ServiceContract and DataContracts and one for the actual WCF service implementation. Then I shared those two libraries containing ServiceContract and DataContracts, and POCOs with the WCF client and tried again, which yielded the same results as before. After trying some other operations which didn't use protobuf-net for serialization, turned out they behaved the same as the first one, resulting in empty fields (!).

The thing was that, I screwed my WCF client's .datasource files while refactoring after I decided to use the assembly sharing technique. So this was a typical PEBKAC, it of course works fine when done properly. Great work with protobuf-net, Marc Gravell!

这篇关于配置 WCF 客户端和服务以与 protobuf-net 一起使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆