Serialização de tipos internos

O WCF permite expor tipos complexos (classes customizadas) através de um serviço. Essas classes precisam estar decoradas com o atributo DataContractAttribute/DataMemberAttribute ou SerializableAttribute, mas se estiver utilizando .NET 3.5 + SP1, você poderá omití-los (POCO).

Esses atributos somente podem ser descartados se a classe que está expondo, tiver seu modificador de acesso definido como public. Repare que no caso abaixo, estou optando por utilizar o modelo POCO, mas a classe está definida como internal, que quer dizer que a mesma somente pode ser acessada a partir do mesmo assembly onde ela foi criada.

[ServiceContract]
internal interface IData
{
    [OperationContract]
    Cliente Ping(Cliente cliente);
}

internal class Cliente
{
    public string Nome { get; set; }
}

Ao rodar a aplicação, resultará na seguinte exeção:

Unhandled Exception: System.Runtime.Serialization.InvalidDataContractException: Type ‘Host.Cliente’ cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute.  See the Microsoft .NET Framework documentation for other supported types.

Para resolver isso, basta definir como public ou, se isso não for coerente, definindo explicitamente os atributos DataContractAttribute/DataMemberAttribute ou SerializableAttribute. Internamente, o WCF valida o tipo em um método chamado IsNonAttributedTypeValidForSerialization, que entre várias verificações, analisa se o tipo está ou não visível fora do assembly onde ele foi criado, recorrendo a propriedade IsVisible da classe Type. E como vimos, caso essa propriedade retorne False, a exceção acima será disparada. As validações para determinar se o tipo contém ou não os atributos de serialização suportados pelo WCF, também são realizadas dentro deste mesmo método.

Limites de Tamanho e Cotas do WCF

As cotas do WCF são mecanismos que utilizamos para evitar o consumo excessivo de recursos que a infraestrutura do WCF utiliza para efetuar a comunicação. Quando bem configuradas, elas evitam que eventuais ataques (DoS) sejam realizados ao teu serviço, que pode prejudicar toda a sua infraestrutura. E, assim como os timeouts, os limites de tamanho e cotas são totalmente configuráveis.

Essas configurações já vem com um valor padrão definido, e que você deve ajustar de acordo com a sua necessidade. Como grande parte dessas configurações são determinadas com números inteiros, e muitas pessoas (talvez para efeito de testes) colocam o valor máximo permitido (int.MaxValue). Provavelmente isso evitará possíveis erros que estejam ocorrendo durante os testes, mas se não se preocupar em ajustá-los de acordo com o volume que o teu serviço trabalha, você não evitará possíveis ataques.

Essas configurações são características específicas de um binding, e que podem ser realizadas de forma declarativa ou imperativa. Abaixo consta a relação dessas configurações (limites e cotas) que são comuns para todos os bindings:

  • maxBufferPoolSize (65.536): Determina, em bytes, a quantidade máxima de memória que será alocada para o processamento da mensagem.
  • maxBufferSize (65.536): Número inteiro que representa a quantidade de bytes do buffer que será usado para armazenar uma mensagem na memória.
  • maxReceivedMessageSize (65.536): Como o próprio nome diz, recebe um número inteiro que corresponde ao tamanho máximo (em bytes) da mensagem que ele (cliente ou serviço) pode receber.
  • readerQuotas: Define alguns critérios para o processamento das mensagens SOAP (XML).
    • maxDepth (32): Um número inteiro que determina a profundidade do aninhamento dos elementos (tipos). Por exemplo, você tem uma classe que possui uma propriedade de outro tipo; essa propriedade representa outro tipo, e assim por diante. Isso será transformado Xml e, consequentemente, a estrutura será representada de forma hierárquica.
    • maxStringContentLength (8.192): O tamanho máximo de uma string que será permitida. Se você tiver strings muito largas, então você precisa alterar esse parâmetro.
    • maxArrayLength (16.384): Um número inteiro que especifica a quantidade máxima de elementos que podemos ter dentro de arrays. Há situações onde o teu serviço te retorna/recebe um array, e se a quantidade de elementos dele for maior do que o valor especificado neste atributo, uma exceção será disparada.
    • maxBytesPerRead (4.096): Define a quantidade de bytes permitida para cada leitura de cada elemento do XML.
    • maxNameTableCharCount (16.384): Quantidade máxima de caracteres permitidas na TableName (XmlDictionaryReader). Todos os nomes dos elementos e atributos retornados são catalogados dentro de uma NameTable. Quando o mesmo nome é retornado várias vezes, a mesma instância da classe string será retornada, tornando o processamento mais eficiente.

Observação: Os valores que estão entre parenteses, são os valores padrão.

Atente-se para estas propriedades. Elas não são publicadas no documento WSDL e, consequentemente, não são automaticamente propagadas para o cliente. Ao fazer a referência de um serviço em uma aplicação cliente, os valores que você visualiza no arquivo de configuração são apenas os valores padrão de cada propriedade. Compete a você alterar para ajustar de acordo com a sua necessidade. Se elas não estiverem sincronizadas ou até com os valores bem dimensionados, dificilmente você conseguirá efetuar a comunicação com sucesso.