Настройка клиента 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
а также DataContract
s и один для фактической реализации службы WCF. Затем я поделился этими двумя библиотеками, содержащими ServiceContract
а также DataContract
s и POCO с клиентом WCF и попытались снова, что дало те же результаты, что и раньше. После попытки некоторых других операций, которые не использовали protobuf-net для сериализации, оказалось, что они вели себя так же, как и первая, что привело к пустым полям (!).
Дело в том, что я ввернул свой клиент WCF .datasource
файлы при рефакторинге после того, как я решил использовать технику обмена сборками. Так что это был типичный PEBKAC, он, конечно, прекрасно работает, когда все сделано правильно. Отличная работа с protobuf-net, Marc Gravell!