Certificação Microsoft 70-487: Objetivo 3.3 – Configure WCF services by using the API

O objetivo 3.3 da certificação Microsoft 70-487, Configure WCF services by using the API, trata dos mesmos aspectos do objetivo anterior. A diferença é que enquanto no objetivo 3.2 vimos como alterar as configurações de um serviço WCF via arquivo de configuração, nesse vamos ver como fazer essas configurações via código, ou API.

Configurando Comportamentos do Serviço

Como configuramos os behaviors no post anterior com ServiceBehaviors e EndpointBehaviors, podemos fazer as mesmas configurações com os atributos ServiceBehaviorAttribute e OperationBehaviorAttribute.
ServiceBehaviorAttribute é do mesmo nível que ServiceContract, então os behaviors são configurados no nível de serviço e se aplicam ao serviço inteiro. OperationBehaviorAttribute se equivale ao OperationContract, ou seja, a cada método do serviço.

ServiceBehaviorAttribute não pode decorar a interface do contrato, somente a implementação (classe). A lista das propriedades pode ser consultada na MSDN.

Uma questão provável de cair no exame é sobre concorrência de ServiceContractAttribute e ServiceBehaviorAttribute. Por exemplo, ambas têm as propriedades Name e Namespace, resultando no seguinte cenário:

[ServiceContract(Namespace="http://www.williamgryan.mobi/Books/70-487", Name="RandomText")]
public interface ITestService{}
[ServiceBehavior(Name="Test", Namespace="http://www.williamgryan.mobi/Books/70-487/Services")]
public class TestService : ITestService
{}

O resultado do envelope do request é o seguinte:

request envelope

Aqui o namespace (xmlns) é o definido no ServiceContract. Note o que acontece no MetadataExchange, quando você adiciona uma referência ao serviço em um client, e no WSDL:

reference

wsdl

Isso mostra que o ServiceBehaviorAttribute seta os valores de Name e Namespace no elemento service dentro do WSDL. Quando definidos no ServiceContract, eles ficam no WSDL.

Configurando Bindings

Assim como no arquivo de configuração, há vários bindings disponíveis para se configurar via API. Todos eles funcionam de forma quase idêntica, apenas com pequenas diferenças. Vamos passar pelos 5 mais comuns, incluindo custom binding.

BasicHttpBinding

BasicHttpBinding é o mais simples dos bindings disponíveis. Funciona via HTTP, precisa da especificação do SOAP 1.1 e tem poucos recursos de segurança. Ele possui 3 construtores:

  • new BasicHttpBinding(): deixa o WCF lidar com os valores default de configuração;
  • new BasicHttpBinding(BasicHttpSecurityMode): recebe um BasicHttpSecurityMode que tem os seguintes valores: None, Transport, Message e TransportCredentialOnly;
  • new BasicHttpBinding(string): recebe o nome de um binding no arquivo de configuração.

wsHttpBinding

Funciona via HTTP como o BasicHttpBinding, mas possui 2 principais vantagens: usa o padrão SOAP 1.2 e tem suporte ao padrão WS-*, que contempla features como Reliable Messaging, transações, comunicação duplex, etc.

wsHttpBinding tem 4 construtores:

  • new WSHttpBinding(): deixa o WCF lidar com os valores default de configuração;
  • new WSHttpBinding(SecurityMode): recebe um BasicHttpSecurityMode que tem os seguintes valores: None, Transport, Message e TransportWithMessageCredential;
  • new WSHttpBinding(string): recebe o nome de um binding no arquivo de configuração;
  • new WSHttpBinding(SecurityMode, bool): recebe um SecurityMode e um bool que habilita ReliableSession

NetMsmqBinding

Implementação quase idêntica à de um wsHttpBinding. A diferença é que usa o enum MsmqSecurityMode para opções de segurança e não há suporte para Reliable Messaging:

  • new NetMsmqBinding(): deixa o WCF lidar com os valores default de configuração;
  • new NetMsmqBinding(NetMsmqSecurityMode): recebe um NetMsmqSecurityModeque tem os seguintes valores: Transport, Message, Both e None;
  • new NetMsmqBinding(string): recebe o nome de um binding no arquivo de configuração.

NetNamedPipeBinding

NetNamedPipeBinding é usado para comunicação entre serviços na mesma máquina, tendo um grande nível de performance.

Como o NetMsmqBinding Implementação quase idêntica à de um wsHttpBinding. A diferença é que usa o enum MsmqSecurityMode para opções de segurança e não há suporte para Reliable Messaging:

  • new NetMsmqBinding(): deixa o WCF lidar com os valores default de configuração;
  • new NetMsmqBinding(NetMsmqSecurityMode): recebe um NetMsmqSecurityModeque tem os seguintes valores: Transport, Message, Both e None;
  • new NetMsmqBinding(string): recebe o nome de um binding no arquivo de configuração.

Custom Binding

Para implementar um binding customizado, você pode usar um ou mais dos predefinidos e adicionar features ou implementar um exclusivo.

O elemento <customBinding> é utilizado para se definir um binding customizado. Há um processo a se seguir com ordem específica:

  1. O item mais externo é o TransactionFlowBindingElement. É opcional, necessário somente se você quiser suportar Flowing Transactions;
  2. ReliableSessionBindingElement, também opcional, indica se Reliable Sessions devem ser suportadas;
  3. SecurityBindingElement define funcionalidades de segurança como autorização, autenticação, proteção, confidencialidade, etc. Também opcional;
  4. O próximo item da cadeia é o CompositeDuplexBindingElement, caso comunicações duplex sejam suportadas;
  5. O elemento OneWayBindingElement pode ser usado se você quiser prover comunicações OneWay para o serviço;
  6. Dois elementos podem ser implementados para prover Stream Security: SslStreamSecurityBindingElement e WindowsStreamSecurityBindingElement. Implementação opcional;
  7. O próximo item se trata do encoding da mensagem. Ao contrário dos outros, esse é obrigatório na implementação de um custom binding. Há 3 encodings disponíveis: TextMessageEncodingBindingElement, BinaryMessageEncodingBindingElement, e MtomMessageEncodingBindingElement;
  8. O último e obrigatório item é o elemento de transporte. Há 8 opções disponíveis: TcpTransportBindingElement, HttpTransportBindingElement, HttpsTransportBindingElement, NamedPipeTransportBindingElement, PeerTransportBindingElement, MsmqTransportBindingElement, MsmqIntegrationBindingElement e ConnectionOrientedTransportBindingElement.

Especificando Contratos

Essa seção é uma revisão de como se define um serviço WCF:

  • Defina um contrato usando o atributo ServiceContract;
  • Defina as operações com o atributo OperationContract;
  • Defina as classes que serão transportadas com os atributos DataContract e DataMember.

Um ponto é que o atributo ServiceContract pode ser definido tanto na classe de implementação quanto na interface, mas não em ambas. Caso isso aconteça, uma InvalidOperationException é lançada ao tentar referenciar esse serviço. O mesmo ocorre com o atributo OperationContract.

O último detalhe é que o construtor padrão do ServiceContract seta 3 propriedades: Name (com o nome da classe), Namespace (com “http://tempuri.org”) e ProtectionLevel para ProtectionLevelNone. É recomendável setar essas propriedades manualmente.

Expondo Metadados

Os atributos usados para se definir um serviço (ServiceContract e OperationContract) não expõem automaticamente os detalhes do serviço (metadados).

Você pode fazer isso definindo endpoints de metadata exchange. Atualmente há 4 bindings disponíveis: mexHttpBinding, mexHttpsBinding, mexNamedPipeBinding e mexTcpBinding (“mex” é uma combinação das palavras “metadata exchange”).

No arquivo de configuração, isso ficaria assim:

<endpoint address="" binding="basicHttpBinding" contract="Samples.WCF.Services.ITestService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />

Note que o atributo contract sempre usa a interface IMetadataExchange.

Para adicionar o endpoint de metadata exchange via API, você pode usar a classe MetadataExchangeBindings. Vamos assumir que você está definindo o seguinte ServiceHost:

String uriString = "http://yourhost/Samples.WCF.Services/TestService/";
Uri[] baseAddresses = new Uri[]{new Uri(uriString)};
ServiceHost hoster = new ServiceHost(typeof(TestService), baseAddresses);

A classe MetadataExchangeBindings tem um método Create para cada binding disponível. Você adicionaria os endpoints assim:

  • Hoster.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexHttpBinding(), “mexBindingHttp”);
  • Hoster.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexHttpsBinding(), “mexBindingHttps”);
  • Hoster.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexTcpBinding(), “mexBindingTcp”);
  • Hoster.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexNamedPipeBinding(), “mexBindingPipe”).

ServiceMetadataBehavior.MexContractName é apenas uma constante com o nome da interface IMetadataExchange.

Chegamos ao fim desse objetivo 🙂
Obrigado pela leitura e fique de olho no post do próximo objetivo, Secure a WCF service.

Até mais!

Leia Mais

Certificação Microsoft 70-487: Objetivo 3.2 – Configure WCF services by using configuration settings

Vários comportamentos importantes de um serviço WCF podem ser configurados por arquivos de configurações. Esses arquivos são muito úteis na construção de uma aplicação, seja web ou WCF. Você pode controlar quase qualquer aspecto de um serviço sem a necessidade de rebuildar e redeployar a aplicação. O objetivo 3.2, Configure WCF services by using configuration settings, mostra que o exame vai cobrar conhecimentos sobre as possíveis configurações e como manipulá-las, no formato de drag and drop. Vamos lá?

Configurando Comportamentos do Serviço

O Visual Studio oferece uma ferramenta chamada WCF Service Configuration Editor, disponível no menu Tools. Ela permite a manipulação de arquivos de configuração visualmente, suportando até a criação de serviços. Segue abaixo o guia básico da criação e configuração de um serviço WCF:

Criando Um Serviço

Você pode escolher a opção Create a New Service, se ainda não tiver um serviço criado:

create a new service

A primeira coisa que o wizard faz é adicionar um elemento e um no arquivo de configuração. Depois, apresenta a página New Service Element Wizard para você selecionar o assembly que contém o serviço:

select assembly

Após especificar o serviço, você precisa especificar os contratos correspondentes:

select contract

Depois, o wizard prossegue para a definição da comunicação do serviço:

select communication mode

Depois, o modo de interoperabilidade (básica ou avançada):

select interoperability

Por último, o wizard pede o endereço do endpoint e apresenta uma página que sumariza todas as informações dadas. O importante aqui é como o arquivo de configuração ficou após esses passos:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="Samples.WCF.Services.TestService">
<endpoint address=http://www.williamgryan.mobi/samples/487
binding="wsDualHttpBinding" bindingConfiguration="" name="DualHttp"
contract="Samples.WCF.Services.ITestService" />
</service>
</services>
</system.serviceModel>
</configuration>

Você pode notar, pelo nome do nodo services, que mais de um serviço pode ser especificado no mesmo projeto WCF. O atributo Name é o tipo (classe) do serviço, e ele precisa ser encontrado no caminho especificado. No elemento endpoint, o ABC é incluído: address, binding e contract.

Expondo Metadados

Endpoints de metadados começam com o prefixo mex, abreviação para metadata exchange. Atualmente há quatro tipos de endpoints: mexHttpBinding, mexHttpsBinding, mexNamedPipeBinding e mexMsmqBinding. Cada um corresponde para o binding do serviço, mas você pode expor todos se necessário.

Vamos alterar o serviço acima para o binding wsHttpBinding e adicionar um endpoint de metadados mexHttpBinding. O XML de configuração ficaria assim:

<service name="Samples.WCF.Services.TestService">
<endpoint address="http://www.williamgryan.mobi/samples/487"
binding="wsHttpBinding" bindingConfiguration="" name="WsHttp"
contract="Samples.WCF.Services.ITestService" />
<endpoint binding="mexHttpBinding" bindingConfiguration=""
name="Mex" contract="IMetadataExchange" />

Com isso, a troca de metados está habilitada, permitindo que os clients vejam o WSDL para obter informações do serviço.

Configurando ServiceBehaviors e EndpointBehaviors

Você especificar comportamentos (behaviors) específicos que habilitam opções avançadas de segurança ou performance, por exemplo. EndpointBehaviors habilitam a configuração dos endpoints, como clientCredentials, o batch de transação, o serializer do DataContract, e muito mais. ServiceBehaviors controlam aspectos do serviço em um nível granular.

Usando o WCF Service Configuration Editor, basta ir para o nodo Advanced, depois Service Behaviors ou Endpoint Behaviors e adicionar um novo behavior. A tela seguintes mostra uma lista dos behaviors disponíveis:

select behaviors

Basta selecionar os behaviors desejados e clicar em Add. O XML ficará parecido com o seguinte:

<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="Debug">
<serviceDebug />
</behavior>
</serviceBehaviors>
</behaviors>

<services>
<service behaviorConfiguration="Debug" name="Samples.WCF.Services.
TestService">
<endpoint address=http://www.williamgryan.mobi/samples/487
binding="wsHttpBinding" bindingConfiguration="" name="WsHttp"
contract="Samples.WCF.Services.ITestService" />
<endpoint binding="mexHttpBinding" bindingConfiguration="" name="Mex"
contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>

O link entre o behavior configurado e o serviço está no atributo behaviorConfiguration do service, conforme destacado acima.

Outro item provável de cair no exame é o ProtocolMapping. Esse elemento permite que você relacione bindings aos protocolos de transporte:

<protocolMapping>
<add scheme="http" binding="basicHttpBinding"/>
<add scheme="net.tcp" binding="netTcpBinding"/>
<add scheme="net.pipe" binding="netNamedPipeBinding"/>
<add scheme="net.msmq" binding="netMsmqBinding"/>
</protocolMapping>

Isso pode ser feito tanto nos arquivos app.config de cada serviço quanto no arquivo geral da máquina de host, o machine.config.

Configurando Bindings

Ainda no WCF Service Configuration Editor, é possível configurar bindings. A tela Create a New Binding lista todas as opções possíveis:

select binding

Após criar um wsHttpBinding, é necessário definir um ProtocolMapping para o protocolo wsHttp. É possível fazer isso selecionando o nodo Protocol Mapping, clicando em New, e informando o Scheme (wsHttp), o Binding (wsHttpBinding) e o Binding Configuration, com o nome do binding criado anteriormente. A configuração no XML ficaria assim:

<protocolMapping>
<add scheme="wsHttp" binding="wsHttpBinding"
bindingConfiguration="wsHttpBindingConfigSample" />
</protocolMapping>
<bindings>
<wsHttpBinding>
<binding name="wsHttpBindingConfigSample" />
</wsHttpBinding>
</bindings>

Nesse caso, configuramos o protocolo wsHttp pelo Protocol Mapping para responder nesse binding. Um cenário mais real seria editar um protocolo já configurado por padrão para responder no nosso binding criado.

Vamos fazer isso com o protocolo http. Pelo editor, basta ir em Protocol Mapping, selecionar o Scheme http e Edit. Após configurar para o nosso binding (wsHttpBinding), o arquivo de configuração ficará assim:

<protocolMapping>
<remove scheme="http" />
<add scheme="http" binding="wsHttpBinding"
bindingConfiguration="wsHttpBindingConfigSample" />
</protocolMapping>

Isso é tudo para esse objetivo 🙂
Para o exame, certifique-se de olhar os exemplos em XML acima e memorizar os atributos possíveis para cada elemento.

O próximo post será sobre o objetivo 3.3, Configure WCF services by using the API.
Até lá!

Leia Mais

Certificação Microsoft 70-487: Objetivo 3.1 – Create a WCF service

Olá pessoal! Chegamos ao capítulo 3 da certificação que fala sobre WCF, a tecnologia primária da Microsoft para o desenvolvimento de aplicações SOA. Esse é o componente com mais conteúdo na certificação e que tem o maior número de objetivos (9).

O primeiro objetivo, Create a WCF service, é a introdução ao assunto e um dos maiores objetivos da certificação. Esse objetivo fala sobre conceitos de SOA, como criar contratos, implementar Message Inspectors e operações assíncronas no serviço.

Conceitos de SOA

Antes de entrar na discussão sobre como construir ou manter serviços, alguns componente de SOA precisam ser definidos. São eles:

  • Service: um componente que é capaz de fazer uma ou mais tarefas;
  • Service definition: um contrato que define uma feature do serviço. O uso de contratos é uma característica determinante de SOA.
  • Binding: item que especifica o transporte, encoding e detalhes do protocolo necessários para se realizar uma conexão cliente-servidor;
  • Serialization: Como SOA envolve comunicação entre diferentes processos, a serialização é necessária para que os dados sejam transferidos e consumidos entre esses processos.

Alguns conceitos universais de SOA são também importantes:

  • Fronteiras são explícitas: para se comunicar com um serviço, você precisa saber algumas coisas dele. Qual é o ponto de entrada do serviço? Quais operações ele suporta? Quais mecanismos de comunicação são aceitas?
  • Serviços são autônomos: o serviço e quaisquer dependências devem ser independentemente deployados, versionados e configurados;
  • Serviços compartilham schemas e contratos, não classes: para se comunicar com um serviço, o client precisa saber a URI, o protocolo de comunicação e quais operações são suportadas. Não é necessária uma cópia das classes ou biblioteca que provê as operações;
  • A compatibilidade de serviços é baseada na política: apesar da WSDL (Web Service Definition Language) prover bastante informação sobre o serviço, é impossível informar todos os requisitos que um serviço pode ter.

Criando Contratos

Como visto acima, um elemento crítico de um serviço SOA é o contrato, para que clients e servers possam se comunicar de forma flexível. Há vários atributos que definem o contrato de um serviço WCF, sendo eles:

  • Atributo ServiceContract: é o que de fato faz um tipo em .NET se tornar um serviço WCF. Sem ServiceContract, não há serviço;
  • Atributo OperationContract: marca uma função como uma operação de um serviço;
  • Atributo DataContract: informa ao runtime que o tipo é serializável;
  • Atributo FaultContract: provê informações de exceptions de uma forma universal.

Criando Um Projeto WCF

Um serviço WCF é basicamente um compilado de classes. Você pode criar um projeto comum, adicionar algumas referências, definir o contrato com os atributos acima e pronto; temos um serviço WCF. Outra forma, mais recomendada, é criar um projeto WCF diretamente no Visual Studio, que vai adicionar as referências necessárias e construir a aplicação de host (com os bindings e muitas configurações feitas por padrão).

Fazendo pelo Visual Studio, você tem as seguintes opções:

WCF Options

As duas opções mais comuns são WCF Service Library e WCF Service Application. A principal diferença entre elas é que WCF Service Library permite que você crie um serviço independente de hospedagem. WCF Service Application inclui um host IIS ou WAS (Windows Activation Service).

Criando um WCF Service Library, o Visual Studio criará um projeto com as referências necessárias, uma classe de serviço e uma interface de serviço com os atributos ServiceContract e OperationContract. Isso é o básico para que um assembly se torne um serviço WCF.

Project

Note as referências à System.Runtime.Serialization e System.ServiceModel, além do arquivo App.config com as configurações do serviço (detalhes em outros objetivos).

Project Properties

Os dois itens de distinção são que o Output type é definido como class library, reforçando que um serviço WCF é como um assembly normal, e que Startup project não é definido pois é possível ter vários serviços em uma solution, e todos eles são independentes.

Definindo O Serviço

Como visto acima, ao se criar um projeto WCF, o Visual Studio cria por padrão uma interface que define o contrato e uma classe que faz a implementação do serviço. A interface deve ser marcada com o atributo ServiceContract, enquanto os métodos devem ter o atributo OperationContract. É possível ter os atributos diretamente na classe que faz a implementação, mas como a classe vai herdar da interface, faz sentido deixar tudo lá (além de facilitar o reuso e desacoplar o contrato da implementação). Para usar tipos complexos, as classes precisam ter o atributo DataContract. Vamos ao código:

Endpoints

Endpoints são essenciais para um serviço WCF, já que a principal característica de um serviço WCF é a exposição dos dados para vários clientes. O termo “ABC” é frequentemente usado na literatura sobre WCF, que significa:

  • A: significa “address”, geralmente uma URI;
  • B: significa “binding”, que define como o transporte dos dados será feito. Bindings são definidos para cada serviço, e um serviço pode ser exposto por vários bindings ao mesmo tempo;
  • C: “contrato”, a interface definida no serviço.

A lista completa dos bindings (que contém vários métodos para HTTP, TCP, etc.) está disponível em http://msdn.microsoft.com/en-us/library/ms730879.aspx.

Pipeline De Processamento WCF

No consumo de um serviço WCF, você sabe que de um lado há um serviço exposto, e do outro lado um client consumindo. Mas o que acontece entre esses processos?

Direta ou indiretamente, uma classe proxy é criada para o client, e essa classe o habilita a interagir com os métodos do serviço. O request é serializado e transferido pelo protocolo definido no binding. Todas as informações precisam ser desserializadas para o serviço consumir, e vice-versa.

Esse processo de serialização e desserialização precisa ser independente de plataforma, já que o serviço pode ser consumido de qualquer linguagem. Por exemplo, uma aplicação Java ou Python não sabe o que é a classe System.Data.SqlClient.SqlDataAdapter. Arrays, pilhas e filas são conhecidas pela maioria das linguagens, não System.Collections.Generic.List, específico para .NET. Isso foi pensado na criação do WCF, então há vários mecanismos para lidar com esse problema. ServiceContract e OperationContract são a primeira parte dessa equação, mas existem várias outras.

Vamos olhar a implementação de um serviço e comentar todos os aspectos que você precisa saber para o exame:

DataContract

Se você olhar para as classes AnswerSet, AnswerDetails, e TestQuestion, vai notar que todas elas têm o atributo DataContract. Esse atributo contém todas as informações sobre como os dados serão serializados.

Para a maioria dos casos, colocar esse atributo já será suficiente para sua classe ser serializada e desserializada pelo WCF e pelo client. Mas há algumas exceções:

  • Se o item sendo enviado deriva de um DataContract, mas não é um DataContract;
  • Se o tipo da informação transmitida for uma interface ou classe abstrata;
  • Se o tipo for um Object.

Nesses 3 casos, geralmente haverá um erro na desserialização da classe. Para deixar a engine de serialização saber sobre um tipo, o atributo KnownType deve ser usado em conjunto com o DataContract.

Vamos ver um exemplo na prática. Suponha que você tenha uma classe base QuestionBase e as derivadas MathQuestion e EnglishQuestion:

[DataContract(Name="English" Namespace="487Samples")]
public class EnglishQuestion : QuestionBase
{}
[DataContract(Name="Math" Namespace="487Samples")]
public class MathQuestion : QuestionBase
{}

Agora suponha que você tenha a seguinte classe que deve ser serializada:

[DataContract(Namespace="487Sample")]
public class QuestionManager
{
[DataMember]
private Guid QuestionSetId;
[DataMember]
private QuestionBase ExamQuestion;
}

Por não saber quais os possíveis tipos para a classe abstrata QuestionBase, ao desserializar essa classe uma exception é lançada. Para resolver esse problema, como dito, o atributo KnownType deve ser usado para indicar quais tipos podem ser usados em uma QuestionBase:

[DataContract]
[KnownType(typeof(EnglishQuestion))]
[KnownType(typeof(MathQuestion))]
public class QuestionManager{}

Tirando as exceções acima, todos os tipos primitivos e algumas classes .NET são serializadas sem problemas pelo DataContractSerializer. A lista completa pode ser vista aqui.

A propriedade Name, não obrigatória, especifica um nome diferente da classe visível para os clients. AnswerSet, por exemplo, tem o Name Answers, e todos os clients enxergarão essa classe como Answers. A propriedade Namespace, também não obrigatória, serve para evitar conflitos de classes com o mesmo Name.

Além do atributo DataContract, é necessário colocar o atributo DataMember (ou EnumMember, no caso de um enum) em cada propriedade a ser serializada.

DataMember

DataMember indica uma propriedade a ser serializada. Se você marcar uma classe com DataContract mas nenhuma propriedade com DataMember, nada será serializado.

Há algumas propriedades opcionais que você pode setar:

  • EmitDefaultValue: indica se, quando uma propriedade tiver o valor default, ela deve ser serializada ou não. O valor padrão é true. Deve ser marcada false para minimizar o tamanho da mensagem a ser serializada (dica para o exame);
  • Name: identifica uma propriedade com um nome diferente para os clients;
  • IsRequired: indica se a propriedade deve ter um valor ao serializar, resultando numa exception caso o valor seja default. O valor padrão é false;
  • Order: especifica a ordem de serialização do membro;
  • TypeId: caso definido numa classe derivada, retorna um identificador único para o atributo.

EnumMember

Funciona exatamente como DataMember, mas se aplica a enums. As propriedades opcionais são:

  • Name: retorna o nome do membro;
  • Value: retorna o valor do membro;
  • BuiltInTypeKind: retorna o tipo;
  • Documentation: obtém ou seta o objeto de documentação, se especificado.

O comportamento de Value é interessante. No caso do enum AnswerDetails, os valores serializados por padrão são A, B, C, D e All. Se você especificar o Value, esse valor será retornado no lugar. Por exemplo:

[DataContract(Namespace="http://www.williamgryan.mobi/Book/70-487")]
[Flags]
public enum AnswerDetails : int
{
[EnumMember(Value="1")]
A = 0x0,
[EnumMember(Value = "2")]
B = 0x1,
[EnumMember(Value = "3")]
C = 0x2,
[EnumMember(Value = "Bill")]
D = 0x4,
[EnumMember(Value = "Ryan")]
All = 0x8
}

FaultContracts

A classe base de exceções do .NET, System.Exception, não é conhecida por potenciais clients de um WCF, como Java ou Python. Cada framework tem sua maneira de lidar com exceptions, mas num cenário distribuído como o consumo de um serviço WCF, todos eles devem se comunicar igualmente tratando-se de erros. Outro problema frequente é que, em um cenário de exception, você provavelmente não gostaria de enviar todo o stack do erro para seus clients por vários motivos. FaultContracts resolvem esses dois problemas de maneira simples. Você pode definir quando e quais informações serão enviadas da exception sem expor seu serviço abertamente.

O método GetQuestionText mostrado anteriormente lançava uma exceção se o número da questão passado fosse igual ou menor a 0:

public String GetQuestionText(Int32 questionNumber)
{
if (questionNumber <= 0) { String OutOfRangeMessage = "Question Ids must be a positive value greater than 0"; IndexOutOfRangeException InvalidQuestionId = new IndexOutOfRangeException(OutOfRangeMessage); throw new FaultException(InvalidQuestionId, OutOfRangeMessage);
}
String AnswerText = null;
// Method implementation
return AnswerText;
}

Para o client saber que uma exceção pode ser lançada por um serviço, isso deve estar definido no contrato do método:

[FaultContract(typeof(IndexOutOfRangeException))]
[OperationContract]
String GetQuestionText(Int32 questionNumber);

Com isso, exceções podem ser enviadas para o client sem problemas de compatibilidade. Se você quiser incluir detalhes sobre o erro, você pode setar a propriedade Reason que aceita um FaultReason.

FaultException tem outra propriedade chamada FaultCode. Com ela é possível especificar erros no nível de SOAP. SOAP 1.1, por exemplo, aceita os erros VersionMismatch, MustUnderstand, Client e Server. SOAP 1.2 provê os erros VersionMismatch, MustUnderstand, DataEncodingUnknown, Sender, e Receiver. Você deve levar em conta o binding do serviço, já que a especificação de mensagem de erro pode ser específico em SOAP 1.1 ou SOAP 1.2.

Ainda outra opção é usar a classe MessageFault, que te dá uma representação da falha em memória, o que permite que você especifique o erro o quanto quiser.

Para o exame, é recomendável se familiarizar com os construtores da classe FaultException. São eles:

  • FaultException(): cria uma FaultException;
  • FaultException(FaultReason): cria e especifica uma Reason;
  • FaultException(MessageFault): cria e especifica uma MessageFault;
  • FaultException(String): cria e usa a string para criar uma FaultReason com o texto;
  • FaultException(FaultReason, FaultCode): cria especificando uma FaultReason e um FaultCode;
  • FaultException(MessageFault, String): cria especificando uma MessageFault e uma string para a action SOAP fault;
  • FaultException(SerializationInfo, StreamingContext): cria especificando informações de serialização e um contexto em que a FaultException será desserializada. Pouco provável de ter no exame;
  • FaultException(String, FaultCode): cria especificando uma FaultReason e um FaultCode;
  • FaultException(FaultReason, FaultCode, String): cria especificando uma FaultReason, um FaultCode e uma string para a action SOAP fault;
  • FaultException(String, FaultCode, String): o mesmo que o anterior, mas a primeira string é transformada em uma FaultReason.

Resumindo, foque em como definir um FaultContract, como lançar uma FaultException e as principais propriedades dela (FaultReason e FaultCode). Tenha em mente também que FaultException herda de CommunicationException.

Implementando Inspectors

Você pode implementar inspectors que estendem o comportamento do WCF.

O primeiro deles é o Parameter Inspector. Com a interface IParameterInspector você consegue validar se os parâmetros atendem à condições customizadas antes que o serviço seja de fato chamado. Por exemplo, se um serviço recebe um parâmetro userName e este deve ser o tamanho maior que 7, um Parameter Inspector poderia ser implementado para evitar chamadas desnecessárias ao serviço.

A interface IParameterInspector tem dois métodos bem intuitivos: BeforeCall e AfterCall. No exemplo acima, fica claro que nossa lógica precisa estar em BeforeCall.

O client precisa ter uma referência à classe que implementa IParameterInspector e adicionar na coleção Behaviors da propriedade Operations da classe proxy:

ProxyInstance.Endpoint.Contract.Operations[0].Behaviors.Add(new UserNameInspector());

Feito isso, é necessário criar o atributo (que herda de Attribute e IOperationBehavior) a ser inserido no contrato e a classe que implementa a interface IParameterInspector:

Outro inspector é o Message Inspector. O propósito dele é você interceptar processamentos dado algum evento. Digamos que seu serviço processa uma compra de um e-commerce. Você dispara uma verificação do cartão de crédito informado e inicia o processamento de salvar todos os dados. No meio do processamento, a operadora do cartão de crédito retornou que este é inválido. Com um Message Inspector, você conseguiria interromper e cancelar o processo que estava ocorrendo de salvar todas as informações.

Para implementar um Message Inspector do lado do server, você usa a interface IDispatchMessageInspector. Ela possui dois métodos:

  • AfterReceiveRequest: chamado após receber um request, mas antes da mensagem ser encaminhada para o método;
  • BeforeSendReply: chamado após o retorno do método mas antes que o retorno seja enviado de fato para o client.

Uma vantagem do Message Inspector é que você o adiciona no pipeline do WCF via arquivo de configuração, evitando que o serviço inteiro seja deployado para que um inspector seja adicionado. Assumindo que a classe ServerInspector implementa a interface IDispatchMessageInspector, você o habilita com a seguinte configuração:

<extensions>
<behaviorExtensions>
<add name="clientSample" type="Samples.WCF.Services.ServerInspector, ClientHost, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>

É possível implementar Message Inspectors do lado do client também, de forma bem parecida com a do server. A interface responsável se chama IClientMessageInspector e tem os seguintes métodos:

  • BeforeSendRequest: chamado antes de um request ser enviado ao serviço;
  • AfterReceiveReply: chamado após o response do server, mas antes de ser encaminhado ao método que o chamou.

Para adicioná-lo no WCF, a configuração é exatamente igual à do server. Para o exame você deve saber que MessageInspectors existem, quais interfaces devem ser implementadas e onde especificá-los no arquivo de configuração.

A finalmente terminamos 🙂

Fique ligado no post do próximo objetivo, Configure WCF services by using configuration settings.
Até mais!

Leia Mais

Certificação Microsoft 70-487: Objetivo 2.5 – Create an Entity Framework data model

Olá!

O objetivo 2.5 da certificação, Create an Entity Framework data model, é o último objetivo do segundo tópico da certificação, Querying and manipulating data by using the Entity Framework. Finalmente vamos ver como construir um Entity Model, além de recordar alguns conceitos e componentes do EF cobertos no objetivo 1.1.

Esse objetivo cobre como: 1) estruturar o model usando as heranças Table-per-Type e Table-per-Hierarchy, 2) escolher e implementar um método de data model (Model First ou Database First), 3) implementar objetos POCO e 4) descrever um data model usando Conceptual Schema Definitions, Storage Schema Definition, e Mapping Language (CSDL, SSDL, MSL). Vamos lá?

Estruturando o Data Model Usando as Heranças Table-per-Type e Table-per-Hierarchy

Você pode tratar os casos de herança de duas formas: Table-per-Type (tabela por tipo) e Table-per-Hierarchy (tabela por hierarquia).

Basicamente, a diferença entre as duas é que, em tabela por tipo, cada classe herdeira vira uma tabela no banco de dados. Em tabela por hierarquia, tanto a classe pai quanto as filhas ficarão guardadas na mesma tabela, geralmente com uma coluna indicando qual é o tipo.

Para o exame, você deve saber quais são as vantagens e desvantagens das duas formas:

Tabela Por Tipo

Vantagens:

  • Os dados não são guardados redundantemente;
  • Os dados são armazenados na terceira norma formal, aumentando a integridade;
  • A implementação é mais simples. Para adicionar ou remover uma entidade herdeira, basta deletar a tabela no banco de dados e atualizar o Entity Model.

Desvantagens:

  • A performance é afetada em operações básicas (CRUD);
  • Complica a administração do banco de dados pelas tabelas adicionais.

Tabela Por Hierarquia

Vantagens:

  • Deixa o schema do banco simplificado por conter menos tabelas;
  • Operações de CRUD otimizadas por trabalhar em uma só tabela.

Desvantagens:

  • Há redundância dos dados por não seguir a terceira normal formal;
  • Pode haver problemas com a integridade dos dados devido a redundância;
  • O Entity Model fica mais complexo pela tabela conter mais campos.

Escolher e Implementar um Método de Data Model (Model First ou Database First)

Escolher um modelo de EDM é um conceito importante, mas não há muito o que aprender aqui.

Model First acontece quando você cria um modelo vazio e vai criando os recursos necessários, como as entidades, os relacionamentos entre elas e o comportamento das propriedades. Depois disso tudo, você gera o schema do banco de dados do EDM; e o Entity Framework cuida da criação do banco de dados e o mapeamento entre o banco e o modelo.

Database First, como esperado, assume que você já tem o schema do banco de dados pronto e escolhe criar o EDM a partir dele. Nesse caso, o Entity Framework cuida da criação dos itens do EDM e o mapeamento entre eles.

Outro ponto importante nesse assunto são as propriedades que você pode setar diretamente no EDM, escolhendo qualquer um dos dois métodos. Clicando com o botão direito no designer e indo em Properties, você pode controlar algumas propriedades que têm o nome bem sugestivo, como se o Lazy Loading deve ser utilizado, o nome do namespace desejado, o nível de acesso dos containers, etc.

Indo nas propriedades de cada entidade ou em alguma propriedade, há alguns comportamentos e configurações que podem ser alterados:

  • A classe base da entidade;
  • Se a propriedade é Nullable;
  • Visibilidade dos getters e setters;
  • Um valor default para uma propriedade;
  • Essa é bem interessante: a propriedade Concurrency Mode, quando setada para Fixed, assegura que o valor no banco de dados não foi alterado quando você vai atualizá-lo. Por exemplo, se você obteve uma entidade do banco com uma propriedade no valor “x” e vai alterá-la para “y”, o Entity Framework vai validar se o valor ainda é “x” antes de você atualizá-lo para “y”. Isso pode ser bem útil em alguns cenários, mas carrega uma piora na performance pela validação. Outro ponto é que, embora esse comportamento seja definido no nível de propriedade, se você tiver 50 campos setados para Fixed, apenas uma query será feita no banco para comparar os 50 valores.

Implementar Objetos POCO

Ao criar uma entidade no EDM, o Entity Framework vai gerar a classe .NET automaticamente. Caso desejado, você pode desabilitar esse comportamento nas Properties do designer e criar você mesmo um POCO (plain old common object), tendo mais controle sobre suas classes.

Para usar suas classes POCO no contexto do EF, alguns passos são necessários. A primeira coisa é que as classes devem ser decoradas com o atributo Table, especificando o nome da tabela no banco de dados:

[Table("Account")]
public class Account{}
[Table("Customers")]
public class Customers{}
[Table("TransactionDetails")]
public class TransactionDetail{}
Table("Transactions")]
public class Transaction{}

O modo de vincular as classes no contexto depende se você estiver utilizando DbContext ou ObjectContext.

Com DbContext, basta definir as classes usando a classe genérica DbSet e passando o tipo de cada POCO:

public partial class EntityFrameworkSamplesEntitiesDb : DbContext
{
public EntityFrameworkSamplesEntitiesDb(): base("name=EntityFrameworkSamplesEntities1")
{}
public DbSet Accounts { get; set; }
public DbSet Customers { get; set; }
public DbSet TransactionDetails { get; set; }
public DbSet Transactions { get; set; }
}

Com ObjectContext não é muito diferente. A classe genérica ObjectSet deve ser usada e as propriedades devem ser inicializadas no construtor:

public partial class EntityFrameworkSamplesEntitiesSample : ObjectContext
{
public EntityFrameworkSamplesEntitiesSample(): base("name=EntityFrameworkSamplesEntities1")
{
Accounts = new ObjectSet();
Customers = new ObjectSet();
TransactionDetails = new ObjectSet();
Transactions = new ObjectSet();
}
public ObjectSet Accounts { get; set; }
public ObjectSet Customers { get; set; }
public ObjectSet TransactionDetails { get; set; }
public ObjectSet Transactions { get; set; }
}

Descrever um Data Model Usando Conceptual Schema Definitions, Storage Schema Definition, e Mapping Language (CSDL, SSDL, MSL)

O Entity Framework junta cada um desses componentes no arquivo .edmx, mas para o exame é importante entender qual o papel de cada um. Basicamente, o CSDL é o conceito do schema do banco traduzido para objetos; o SSDL representa o próprio schema do banco de dados e o MSL é o responsável por juntar os dois e mapear o schema do banco com os objetos.

Não há muito o que ir mais fundo sobre o CSDL e o SSDL, a não ser que são XMLs representando informações – objetos e modelos relacionais de banco de dados. O MSL, que unifica os dois, é um pouco mais complexo e merece um entendimento maior.

O MSL, junto com o .edmx, contém vários outros arquivos gerados automaticamente. São eles:

  • Um arquivo .edmx que serve como container para tudo;
  • Um arquivo ModelName.Context.tt para geração de templates das classes .NET especificadas no model do respectivo contexto;
  • O arquivo ModelName.Context.cs que contém as classes do contexto;
  • O arquivo ModelName.Designer.cs que provê suporte para o designer do EDM;
  • Um arquivo ModelName.edmx.Diagram que contém um XML parecido com o seguinte:
    <?xml version="1.0" encoding="utf-8"?>
    <edmx:Edmx Version="2.0" xmlns:edmx="http://schemas.microsoft.com/ado/2008/10/edmx">
    <!-- EF Designer content (DO NOT EDIT MANUALLY BELOW HERE) -->
    <edmx:Designer xmlns="http://schemas.microsoft.com/ado/2008/10/edmx">
    <!-- Diagram content (shape and connector positions) -->
    <edmx:Diagrams>
    <Diagram DiagramId="8576b1de6991436caba647ac89886831" Name="Diagram1">
    <EntityTypeShape EntityType="EntityFrameworkSamplesModel1.Account" Width="1.5" PointX="7.5" PointY="0.875" IsExpanded="true" />
    <EntityTypeShape EntityType="EntityFrameworkSamplesModel1.Customer" Width="1.5" PointX="9.75" PointY="0.875" IsExpanded="true" />
    <EntityTypeShape EntityType="EntityFrameworkSamplesModel1.TransactionDetail" Width="1.5" PointX="14.25" PointY="0.875" IsExpanded="true" />
    <EntityTypeShape EntityType="EntityFrameworkSamplesModel1.Transaction" Width="1.5" PointX="12" PointY="0.875" IsExpanded="true" />
    <AssociationConnector Association="EntityFrameworkSamplesModel1.FK_Account_Account" ManuallyRouted="false" />
    <AssociationConnector Association="EntityFrameworkSamplesModel1.FK_Customer_Account" ManuallyRouted="false" />
    <AssociationConnector Association="EntityFrameworkSamplesModel1.FK_Transactions_Customer" ManuallyRouted="false" />
    <AssociationConnector Association="EntityFrameworkSamplesModel1.FK_TransactionDetails_Transactions" ManuallyRouted="false" />
    </Diagram>
    </edmx:Diagrams>
    </edmx:Designer>
    </edmx:Edmx>
  • Por último, o arquivo ModelName.tt que tem as informações de template para geração das classes de dados.

Uma olhada no arquivo .edmx pode ajudar na visualização de todos esses artefatos:

edmx

Isso é tudo para esse objetivo. O próximo post cobrirá o primeiro objetivo do capítulo 3 da certificação, Designing and implementing WCF Services.

Nos vemos lá!

Leia Mais

Certificação Microsoft 70-487: Objetivo 2.4 – Query and manipulate data by using ADO.NET

Olá pessoal! Esse post será sobre objetivo 2.4 da certificação Microsoft 40-487, Query and manipulate data by using ADO.NET. Embora já coberto no objetivo 1.1, esse objetivo é bem específico sobre o ADO.NET e suas classes, assim como métodos assíncronos.

Fazendo Queries com Connection, DataReader, Command, DataAdapter e DataSet

SqlConnection

Não há muito segredo sobre a classe SqlConnection. Ela herda de IDbConnection e você deve instanciá-la passando uma connection string, seja inline ou de um arquivo de configuração (web.config).

Todas as connections precisam ser abertas com o método Open e fechadas explicitamente com Close ou num bloco using.

using (SqlConnection connection = new SqlConnection("ConnectionStringName"))
{
connection.Open();
}

Sempre abra uma conexão num bloco try/catch, pois fatores fora do seu alcança podem impedir que isso aconteça.

SqlCommand

A classe SqlCommand, herdeira de IDbCommand, é a responsável por fazer comandos ao banco de dados. Algumas de suas características são:

  • Uma SqlCommand precisa de uma SqlConnection aberta para operar;
  • Além da propriedade Connection, o único outro item importante é setar a propriedade CommandText, que é o SQL a ser executado. É possível também passar somente o nome de uma stored procedure no lugar do SQL, sendo necessário nesse caso setar a propriedade CommandType para StoredProcedure;
  • Você nunca deve concatenar valores como string numa query SQL. Instancie a classe SqlParameter e o adicione na propriedade Parameters;
  • Há muito debate entre stored procedures e “SQLs dinâmicos” (queries na propriedade CommandText). O fato é que, sendo parametrizados corretamente, o plano de execução dos SQLs dinâmicos é cacheado, o que pode aumentar a performance e ser menos vulnerável à ataques;
  • Há várias maneiras de executar um SqlCommand. Você pode usá-lo como input da propriedade SelectCommand de um SqlDataAdapter, chamar ExecuteScalar para retornar um valor ou ExecuteReader para retornar um SqlDataReader;
  • Cada método de execução tem seu par assíncrono, indicados pelo nome começando com Begin;
  • É bom chamar o método Dispose de um SqlCommand. Faça isso explicitamente ou faça uso de um bloco using.

SqlDataReader

A classe SqlDataReader, invocada pelo método ExecuteReader de um SqlCommand, é responsável pela obtenção de dados. Você deve fechar explicitamente quaisquer SqlDataReader abertos.

SqlDataReader tem uma característica que você deve ter em mente: ao obter um SqlDataReader pelo método ExecuteReader, o buffer é carregado do banco de dados, mas o stream dos dados não é feito até você iterar pelos itens com o método Read. É possível saber se há itens no buffer com a propriedade HasRows, mas não há como saber quantos itens existem sem iterar pelos dados, o que esvazia o buffer no processo. Isso significa que você só pode iterar pelos dados uma única vez.

SqlDataAdapter

SqlDataAdapter é uma classe designada para fazer todas as operações no banco de dados (CRUD).

Na leitura, ela funciona bem diferente do SqlDataReader: um DataSet é populado (com uma mais DataTables), permitindo que você itere quantas vezes quiser – até com LINQ. Outra diferença é que você pode obter facilmente a quantidade de registros retornados com o Count de uma DataTable.

O método Fill é o jeito mais básico de carregar um SqlDataAdapter. Passando um SqlCommand válido, o método conecta ao banco de dados, executa o comando e cria uma DataTable para cada query SELECT.

Cada coluna gera uma DataColumn na DataTable sendo construída. O próprio adapter infere o tipo da DataColumn, de acordo com o schema no banco de dados. Se houver mais de uma coluna com o mesmo nome ou alias, o adapter adiciona um número no final do nome (FirstName1, FirstName2, FirstName3). É possível também chamar o método Fill especificando o valor AddWithKey do enum MissingSchemaAction, resultando na adição de uma primary key e outras contraints existentes no banco de dados. Caso haja mais de uma primary key numa query, você pode criar uma manualmente na DataTable. Com a DataTable e as DataColumns criadas, o adapter cria uma DataRow para cada registro retornado do banco de dados.

Outro método importante é o Update. Ele funciona assim: todas as DataRows, quando retornadas, tem o RowState como Unchanged. Você pode alterar o RowState de uma DataRow para Added, Modified ou Deleted. Basicamente, quando o método Update é chamado, ele varre todas as DataRows e, dependendo do RowState, chama o SqlCommand correspondente (InsertCommand, UpdateCommand ou DeleteCommand) do SqlDataAdapter. Para funcionar corretamente, você precisa setar todas essas propriedades. Caso o command não seja encontrado, uma exceção é lançada.

A sintaxe do SqlDataAdapter é assim: (note que o próprio método Fill chama o método Open da SqlConnection, e ele se encarrega de fechá-la. Caso você a abra, a responsabilidade de fechá-la é sua)

Por último, você pode definir o comportamento de um SqlDataAdapter com algumas propriedades:

  • AcceptChangesDuringFill: por padrão, o método Fill carrega todas as DataRows com o RowState Unchanged. Se você setar essa propriedade como false, todas as DataRows terão o RowState Added;
  • AcceptChangesDuringUpdate: como dito anteriormente, o método Update examina o RowState de cada DataRow para saber qual Command invocar. Ao final do método, todos os RowStates são retornados para Unchanged. Setar essa propriedade como false não reseta o RowState; mantém o estado que estava antes do método Fill ser chamado;
  • UpdateBatchSize: por padrão, o SqlDataAdapter atualiza uma DataRow por vez. Setando essa propriedade, as atualizações são feitas em grupos, o que diminui as chamadas ao banco de dados;
  • OnRowUpdating/OnRowUpdated: eventos na atualização de uma DataRow;
  • ContinueUpdateOnError: por padrão, quando há vários comandos a serem executados e um deles lança uma Exception, todos os outros comandos não são feitos. Setando essa propriedade como true, o comando que deu erro é ignorado e os outros comandos são executados normalmente;
  • FillSchema: FillSchema usa o SelectCommand para construir o schema do DataTable correspondente, sem popular a tabela.

Fazendo Operações Síncronas e Assíncronas

Há três métodos de um SqlCommand que têm suas partes assíncronas: BeginExecuteReader, BeginExecuteXmlReader e BeginExecuteNonQuery. Eles são bem fáceis de usar. Basta examinar a propriedade IsCompleted e, quando completo, chamar o método End correspondente:

Obrigado pela leitura e fiquem ligados no post do objetivo 2.5, Create an Entity Framework data model.

Até mais!

Leia Mais

Certificação Microsoft 70-487: Objetivo 2.3 – Query data by using LINQ to Entities

Olá pessoal! Chegamos ao objetivo 2.3 da certificação Developing Microsoft Azure and Web Services, o Query data by using LINQ to Entities.

Esse objetivo cobre como 1) fazer queries usando LINQ, 2) IQueryable vs. IEnumerable e 3) logar queries. Vamos lá?

Fazendo queries usando LINQ

O objetivo desse ponto é mostrar que é possível utilizar LINQ to Entities de versões antigas do Entity Framework com ObjectContext, assim como se faz com DbContext.

Com ObjectContext, você cria um ObjectQuery que é virtualmente idêntico à aqueles do DbContext. Por exemplo:

IQueryable query = from acct in Context.Accounts
where acct.AccountAlias == "Primary"
select acct;

É importante também saber que o ObjectQuery implementa IQueryable, IEnumerable e IListSource.

IQueryable vs. IEnumerable

IEnumerable é caracterizado por:

  • Uso em LINQ to Objects e LINQ to XML;
  • São performados em memória;
  • São performados no heap.

IQueryable são notavelmente diferente na maioria dos casos:

  • Rodam fora do processo;
  • Suportam diferentes datasources, incluindo remotos;
  • São usados em LINQ to Entities e LINQ to DataServices.

Logando Queries

Usando Entity Framework, às vezes se torna necessário você saber exatamente qual a query SQL o framework gerou automaticamente. Por exemplo:

IQueryable Query = from Acct in Context.Accounts
where Acct.AccountAlias == "Primary"
select Acct;

Com ObjectQuery (ObjectContext), você pode obter a query fazendo um cast explícito para ObjectQuery e usar o método ToTraceString:

String Tracer = (Query as ObjectQuery).ToTraceString();

Com DbContext, é possível de maneira similar:

String Output = (from Acct in ContextName.ContextSet select Acct).ToString();

Isso é tudo para o conteúdo desse objetivo.
Obrigado pela leitura e acompanhe a continuação das postagens da certificação Microsoft 70-487, que será sobre o próximo objetivo Query and manipulate data by using ADO.NET.

Até mais!Query and manipulate data by using ADO.NET

Leia Mais

Certificação Microsoft 70-487: Objetivo 2.2 – Query and manipulate data by using Data Provider for Entity Framework

Olá pessoal! Continuando a série de posts sobre a certificação Microsoft 70-487, vamos ao objetivo 2.2, Query and manipulate data by using Data Provider for Entity Framework. Esse é um objetivo bem curto que fala sobre como fazer queries usando o namespace System.Data.EntityClient.

O namespace System.Data.EntityClient é um superset do namespace System.Data, e suas classes operam de forma bem similar. As diferenças se referem ao Entity Framework.

Em todos os cenários de acesso ao banco de dados, uma conexão é necessária. No namespace SqlClient, há a classe SqlConnection. Para o EntityClient, a classe de conexão é a EntityConnection. Ela tem três construtores: um vazio (new EntityConnection()), um que recebe uma connection string normal e o terceiro em que você deve especificar informações (metadados) para o EDM. Para fazer isso, a melhor maneira é usar a classe EntityConnectionStringBuilder em conjunto com a classe SqlConnectionStringBuilder:

String provider = "System.Data.SqlClient";
String serverName = "localhost";
String databaseName = "dbname";

SqlConnectionStringBuilder connBuilder = new SqlConnectionStringBuilder();
connBuilder.DataSource = serverName;
connBuilder.InitialCatalog = databaseName;

EntityConnectionStringBuilder efBuilder = new EntityConnectionStringBuilder();
efBuilder.Provider = Provider;
efBuilder.ProviderConnectionString = connBuilder.ToString();
efBuilder.Metadata = @"*csdl, *ssdl, *msl";
EntityConnection Connection = new EntityConnection(efBuilder.ToString());

O resto opera de forma bem igual à uma SqlConnection, como os métodos Open, Close e BeginTransaction.

O EntityCommand também trabalha como o SqlCommand. Há os métodos CreateParameter, ExecuteNonQuery, ExecuteReader e ExecuteScalar, por exemplo, e todos eles funcionam como sugerido. Todos eles também têm suas versões Async, onde a única diferença é a presença da propriedade CancellationToken.

Há também a classe EntityTransaction, que funciona também de forma idêntica à TransactionScope (explicada aqui).

Fazendo Transações

Há duas opções para se fazer transações diretamente.

A primeira é usando a classe DbTransaction. Você a obtém pelo DbContext, no método Database.Connection.BeginTransaction():

EntityFrameworkSamplesEntities Context = new EntityFrameworkSamplesEntities();
IDbTransaction TransactionInstance = Context.Database.Connection.BeginTransaction();
try
{
// Do Something
Context.SaveChanges();
TransactionInstance.Commit();
}
catch (EntityCommandExecutionException)
{
TransactionInstance.Rollback();
}

O próximo método, bem mais provável de se encontrar no exame, é pela classe TransactionScope. A vantagem dela é que você só precisa chamar o método Complete após SaveChanges; não é necessário nenhum tratamento especial em caso de falha:

EntityFrameworkSamplesEntities Context = new EntityFrameworkSamplesEntities();
using (TransactionScope CurrentScope = new TransactionScope())
{
try
{
Context.SaveChanges();
CurrentScope.Complete();
}
catch (EntityCommandExecutionException)
{
//Handle the exception as you normally would
//It won't be committed so transaction wise you're done
}
}

E isso é tudo para esse objetivo 🙂 O próximo post será sobre o objetivo 2.3, Query data by using LINQ to Entities.

Até mais!

Leia Mais

Certificação Microsoft 70-487: Objetivo 2.1 – Query and manipulate data by using the Entity Framework

Olá pessoal! Esse será o post do primeiro objetivo do segundo grande tópico da certificação, o Querying and manipulating data by using the Entity Framework. Esse tópico foca especificamente em LINQ e Entity Framework e cobre aproximadamente 20% das questões do exame. Esse primeiro objetivo, Query and manipulate data by using the Entity Framework (que recebe quase o mesmo nome do tópico), cobre aspectos específicos do EF e os componentes que o compõe. Vamos lá?

Fazendo Queries, Updates e Deletes Usando DbContext

Fazer queries usando DbContext é bem direto e simples, usando LINQ:

var query = (from acct in context.Accounts
where acct.AccountAlias == "Primary"
select acct).FirstOrDefault();

Após uma entidade ser retornada de uma query, você pode interagir com o objeto como qualquer outro. Você faz quaisquer modificações necessárias e chama o método SaveChanges do context para fazer um update das informações alteradas.

É possível também atualizar um item que você criou usando o método Add ou Attach e chamando o mesmo método SaveChanges para persistir as alterações. É importante entender bem a diferença entre os dois métodos: incluir uma entidade usando Add faz o item ter o EntityState como Added. Pelo objeto não ter valores originais no ObjectStateEntry, quando SaveChanges é chamado, o contexto vai tentar inserir a entidade no banco de dados. Attach, por outro lado, cria o item com o EntityState Unchanged. Então o momento que você faz o Attach é muito importante. Se você fizer depois de todas as alterações, nada vai acontecer, pois o EntityState vai continuar como Unchanged. Se você fizer o Attach e depois fazer as modificações na entidade, aí sim o context vai fazer o update das propriedades no banco na chamada do SaveChanges.

Para deletar uma entidade do banco, você deve especificar o tipo da entidade usando o método Set e depois chamar o método Remove passando uma query. O item será deletado após a chamada ao SaveChanges.

context.Set().Remove(query);

Deferred Execution e Lazy Loading

Esse assunto pode ser bem extenso, por isso vamos simplificar bastante aqui.

Veja o seguinte código:

var query = from acct in context.Accounts
where acct.AccountAlias == "Primary"
select acct;

Suponha que no banco de dados há 20 registros com o nome da conta Primary. É lógico pensar que na próxima linha o count da query será 20, certo? Errado. Nesse ponto, nada aconteceu em termos de banco de dados. A essência de Deferred Execution (execução adiada) é que somente quando você pede o dado, explicitamente referenciando-o, que o acesso ao banco de dados é de fato feito. O código abaixo, por exemplo, potencialmente dispara muitas idas ao banco de dados:

var query = from acct in Context.Accounts
where acct.AccountAlias == "Primary"
select acct;
foreach (var currentAccount in query)
{
Console.WriteLine("Current Account Alias: {0}", currentAccount.AccountAlias);
}

Já o código abaixo, por exemplo, ao invés de retornar uma coleção de itens, retorna somente um valor (count). Como é impossível obter esse valor sem ir ao banco de dados, ao chamar Count (ou qualquer função Aggregate como Average e Max), o acesso ao banco de dados é feito imediatamente:

var query = (from acct in context.Accounts
where acct.AccountAlias == "Primary"
select acct).Count();

Resumindo: no conceito de Deferred Execution, usado pelo EF, dados só são recuperados quando necessário.

O Lazy Loading é um conceito parecido com o Deferred Execution, mas aplicado para relacionamentos no banco de dados.

Imagine uma estrutura onde um Bank tem vários Customers, um Customer tem várias Accounts, uma Account tem várias Transactions e uma Transaction tem várias TransactionDetails. Ao obter um Customer para uma atualização simples de dados, você iria querer que todas as Accounts (com todas as Transactions e as TransactionsDetails) fossem retornadas? Provavelmente não.

As opções de Loading são:

  • Lazy Loading: quando uma query é executada, somente os dados da entidade alvo são retornados. Por exemplo, se houver um relacionamento entre Account e Contact, uma query para Account retornaria inicialmente os dados da Account. Informações da Contact associada seriam retornadas quando uma NavigationProperty fosse acessada ou se você explicitamente pedisse;
  • Eager Loading: todos os dados relacionados são retornados. Nesse exemplo, ao fazer uma query para uma Account, os dados da Contact já seriam retornados. Essencialmente, é o oposto de Lazy Loading;
  • Explicit Loading: quando Lazy Loading está desabilitado, você pode usá-lo para carregar entidades de forma Lazy explicitamente. O método Load permite que você execute queries individualmente.

Você pode achar que Lazy Loading sempre é a melhor opção, mas não se engane. Se você sabe quais dados vai usar, o Lazy Loading pode afetar a performance por fazer várias idas as banco de dados ao invés de uma. Se por outro lado você não tiver certeza do número de requisições ao banco de dados e provavelmente não vai utilizar todos os dados retornados, aí sim o Lazy Loading é a melhor opção.

É possível habilitar ou desabilitar o Lazy Loading visualmente ou por código. Pelo Designer, basta ir ao EF Model, depois Properties, e escolher True ou False na propriedade Lazy Loading Enabled. Por código, o DbContext possui uma propriedade Configuration.LazyLoadingEnabled: context.Configuration.LazyLoadingEnabled = true;.

Dê uma olhada no código abaixo:

Se você colocar um breakpoint na linha do primeiro foreach, vai perceber bem a diferença do Lazy Loading. Caso esteja desabilitado, toda a cascata de relacionamento será carregada na primeira ida ao banco de dados. Caso habilitado, você vai notar que as propriedades Customers, Transactions e TransactionDetails não serão carregadas. Elas irão sendo carregadas conforme forem usadas (foreach). Algumas outras operações também fazem com que os relacionamentos sejam carregados, como ToList, ToArray ou ToDictionary.
É importante notar também que se uma função Aggregate for chamada, uma query será executada imediatamente para retornar aquele valor. Então se você chamar o método Count de query.Customers e depois iterar pela lista (foreach), duas chamadas ao banco de dados serão feitas. Iterando primeiro (ou chamando o método ToList), você já saberia o tamanho da lista e não seriam necessárias duas chamadas.

Criando Compiled Queries

Usando LINQ, o EF cuida de traduzir a query para a linguagem SQL em tempo de execução, o que leva algum tempo. Por isso, o EF suporta compiled queries, onde ele cacheia o SQL de uma determinada query e somente muda os parâmetros na hora de executá-la. A partir do .NET 4.5, isso é feito automaticamente, mas você pode ainda fazer esse processo manualmente:

static readonly Func compiledQuery =
CompiledQuery.Compile(
(ctx, email) => (from p in ctx.People
where p.EmailAddress == email
select p).FirstOrDefault());

E usar a query assim: Person p = compiledQuery.Invoke(context, "foo@bar");. É importante o método ser estático para evitar a compilação a cada chamada. Outro ponto é que se a query for modificada, o EF vai precisar recompilá-la. Então o ganho de performance será perdido se você adicionar um Count ou ToList numa query compilada.

Usando Entity SQL

O EF permite que você execute queries SQL diretamente no DbContext, com todo o conhecimento do seu model. Outro benefício é o maior controle sobre a query SQL que será executada no banco de dados. Você pode fazer uso do Entity Framework Object Services assim:

var queryString = "SELECT VALUE p " +
"FROM MyEntities.People AS p " +
"WHERE p.FirstName='John'";
ObjectQuery people = context.CreateQuery(queryString);

Outro cenário é quando você não precisa do objeto inteiro ou quer trabalhar somente com os valores retornados para ganho de performance. Isso é efetuado através da classe EntityDataReader, bem parecida com a classe DataReader do System.Data. Segue um código de exemplo:

Isso é tudo para esse objetivo. Obrigado pela leitura e fique de olho no próximo post, que será sobre o objetivo 2.2 da certificação Microsoft 70-487: Query and manipulate data by using Data Provider for Entity Framework.

Até mais!

Leia Mais

Certificação Microsoft 70-487: Objetivo 1.6 – Manipulate XML data structures

Olá pessoal!

Continuando a série sobre a certificação Microsoft 70-487, esse post será sobre o objetivo 1.6, Manipulate XML data structures, o último do primeiro tópico da certificação, Accessing data. Vamos lá?

Lendo, Filtrando, Criando e Modificando Estruturas XML

XML é um arquivo que estrutura dados em formato de texto baseado em elementos e atributos. Elementos são estruturas que representam um componente de dados, enquanto atributos são propriedades dos elementos.

O primeiro componente de um documento XML é o XML declaration. Esse elemento não é obrigatório e geralmente inclui a versão do XML e o encoding:

<?xml version="1.0" encoding="utf-8" ?>

Um XML válido tem os seguintes requerimentos:

  • Deve ter somente um elemento raiz;
  • Elementos que são abertos devem ser fechados na ordem que foram abertos;
  • Todos os elementos precisam ser válidos ou bem formados.

Considere o seguinte XML. Name, FirstName, MiddleInitial e LastName são elementos, enquanto MiddleName é um atributo do elemento MiddleInitial:

<?xml version="1.0" encoding="utf-8" ?>
<Name>
<FirstName>John</FirstName>
<MiddleInitial MiddleName="Quartz">Q</MiddleInitial>
<LastName>Public</LastName>
</Name>

Existem mais dois elementos que você deve conhecer para o exame: comentários e namespaces. Comentários são definidos dessa forma:

<!-- this is a comment about the Name element. Blah blah blah-->

Namespaces servem para identificar quem escreveu o XML, geralmente uma empresa. Você pode definir o namespace no elemento raiz como o seguinte:

xmlns:Prefix="SomeValueUsuallyACompanyUrl"

xmlns é a abreviação de XML NameSpace.

Você pode definir também dois namespaces no elemento raiz. Isso é útil quando duas ou mais pessoas (ou empresas) estão escrevendo o mesmo XML, talvez com elementos com o mesmo nome. Por exemplo:

<DocumentCore xmlns:johnco="http://www.yourcompany.com/Companies" xmlns:billco="http://
www.mycompany.com/Customers">
<johnco:Name>
<johnco:Company>JohnCo</johnco:Company>
</johnco:Name>
<billco:Name>
<billco:FirstName>John</billco:FirstName>
<billco:MiddleInitial>Q</billco:MiddleInitial>
<billco:LastName>Public</billco:LastName>
</billco:Name>
</DocumentCore>

Outra opção é definir o namespace no nível de cada elemento:

<DocumentCore>
<johnco:Name xmlns:johnco="http://www.yourcompany.com/Companies">
<johnco:Company>JohnCo</johnco:Company>
</johnco:Name>
<billco:Name xmlns:billco="http://www.mycompany.com/Customers">
<billco:FirstName>John</billco:FirstName>
<billco:MiddleInitial>Q</billco:MiddleInitial>
<billco:LastName>Public</billco:LastName>
</billco:Name>
</DocumentCore>

Manipulando Dados em XML

Para o exame, você deve se familiarizar com 5 classes que manipulam XML no .NET Framework: XmlWriter, XmlReader, XmlDocument, XPath e LINQ-to-XML (XElement e XDocument). O exame foca mais em LINQ-to-XML por ser mais recente e elegante, então dê atenção especial a esse tópico.

XmlWriter

XmlWriter é uma classe fácil, antiga e intuitiva de se entender. Você obtém uma instância pelo método estático Create passando um nome de arquivo e chama os métodos WriteStartDocument, WriteStartElement ou WriteElementString, WriteEndElement e WriteEndDocument. Entenda rapidamente como a classe funciona e o XML gerado:

<?xml version="1.0" encoding="UTF-8"?>
<Customers>
<Customer>
<FirstName>John</FirstName>
<MiddleInitial>Q</MiddleInitial>
<LastName>Public</LastName>
</Customer>
<Customer>
<FirstName>Bill</FirstName>
<MiddleInitial>G</MiddleInitial>
<LastName>Ryan</LastName>
</Customer>
<Customer>
<FirstName>William</FirstName>
<MiddleInitial>G</MiddleInitial>
<LastName>Gates</LastName>
</Customer>
</Customers>

XmlReader

XmlReader é a contraparte do XmlWriter, e é também simples de usar. Você cria um XmlReader passando o nome do arquivo que quer ler, faz um while chamando o método Read e faz sua operação olhando para a propriedade NodeType. Uma rápida olhada no código vai deixar as coisas mais claras:

XmlDocument

XmlDocument é o “pai” dos antigos XmlWriter e XmlReader, ainda mais fácil de usar. Para ler um XML, você instancia um novo XmlDocument, chama o método Load apontando para um arquivo ou stream, extrai a lista de nodos (elementos) e itera por eles.

Para escrever um arquivo XML, basta criar elementos com o método CreateElement e dar AppendChild neles, respeitando a regra de que os elementos abertos primeiro devem ser fechados por último.

XPath

XPath, que significa XML Path Language, é um tipo de query para documentos XML. Você obtém a classe XPathNavigator chamando o método CreateNavigator de um XmlDocument.

Considere o seguinte XML:

<?xml version="1.0" encoding="utf-8" ?>
<People>
  <Person firstName="John" lastName="Doe">
    <ContactDetals>
      <EmailAddress>john@unknown.com</EmailAddress>
    </ContactDetals>
  </Person>
  <Person firstName="Jane" lastName="Doe">
    <ContactDetals>
     <EmailAddress>jane@unknown.com</EmailAddress>
      <PhoneNunmber>001122334455</PhoneNunmber>
    </ContactDetals>
  </Person>
</People>

Para obter os elementos Person pelo firstName, você usaria o seguinte código:

LINQ-to-XML

LINQ-to-XML será bastante coberto no exame pois usa bastante os recursos modernos do .NET Framework como método anônimos, genéricos, tipos nullable e a semântica de query LINQ. É bem importante que você conheça bem a sintaxe do LINQ para o exame como um todo.

As classes estão no namespace System.Xml.Linq. Uma das mais importantes é a classe XElement, que tem 5 construtores:

  • public XElement(XName someName);
  • public XElement(XElement someElement);
  • public XElement(XName someName, Object someValue);
  • public XElement(XName someName, params Object[] someValueset);
  • public XElement(XStreamingElement other);
    /li>

Lembra de como você cria um elemento Customer dentro de Customers com XmlDocument? Veja como se faz com XElement:

XElement customers = new XElement("Customers", new XElement("Customer",
new XElement("FirstName", "John"), new XElement("MiddleInitial", "Q"),
new XElement("LastName", "Public")));

Outra vantagem é que XElement faz o escape automaticamente de caracteres usados na semântica do XML como <, > e &.

Há também a classe XDocument, que representa o documento XML por inteiro. Ela contém as seguintes características:

  • Um objeto XDeclaration quer permite a especificação da versão do XML e do encoding;
  • Um objeto XElement que serve como raiz;
  • Objetos de XProcessingInstruction que representam instruções de processamento do XML;
  • Objetos de XComment que representam comentários.

Você pode instanciar um XAttribute da seguinte forma: XAttribute sampleAttribute = new XAttribute("FirstName", "John");.

Namespaces, representados pela classe XNamespace , são usados assim:

XNamespace billCo = "http://www.billco.com/Samples";
XElement firstNameBillCo = new XElement(billCo + "FirstName", "John");

As coisas começam a ficar mais difíceis nas queries com LINQ, e isso é uma coisa que “can’t be emphasized enough”: é muito importante saber LINQ e suas nuances, não só para esse objetivo.

Uma característica é entender a diferença entre os métodos Descendants e Elements de um XContainer. Considere o seguinte código:

O output de ambos os casos é 3. Inicialmente eles podem parecer idênticos, mas eles têm uma grande diferença. O método Descendants nesse caso procura por elementos Customer em qualquer nível abaixo do XContainer que o chamou, nesse exemplo o Root. Já o Elements procura por elementos Customer apenas um nível abaixo do XContainer. Nesse caso ele encontra os três elementos Customer pois o Root é o elemento Customers. Se a estrutura fosse como abaixo, por exemplo, o método Elements não encontraria nenhum elemento Customer:

<Root>
<Customers>
<Customer><FirstName>John</FirstName></Customer>
<Customer><FirstName>Bill</FirstName></Customer>
<Customer><FirstName>Joe</FirstName></Customer>
</Customers>
</Root>

Manipulação Avançada de XML

Nessa seção do objetivo, a ideia é ter mais questões de manipulações e queries com LINQ-to-XML.

Um ponto a destacar é que você deve conhecer a classe XmlConvert para dar escape de caracteres especiais se você não estiver usando uma classe que faz isso para você, como XElement. XmlConvert é irmã da classe Convert, focada para XML. Ela automaticamente faz o escape de caracteres reservados e provê vários métodos de conversão para arquivos XML.

A lista abaixo contém os métodos não óbvios como ToInt32, ToDateTime, etc. e seus comportamentos:

  • DecodeName: faz o decode de um nome encodado;
  • EncodeName: faz o encode de uma string para que possa ser usada como um nome de nodo XML;
  • EncodeLocalName: faz o mesmo de EncodeName, com uma diferença: encoda também o caracter “:”, assegurando que o nome pode ser usado em elementos que contenham namespace;
  • EncodeNmToken: retorna um nome válido para um nodo de acordo com a XML Language Spec;
  • IsStartNCNameChar: determina se o parâmetro não é o caracter “:”;
  • IsPublicIdChar: determina se o parâmetro é um identificador XML público (algumas magic strings que existem por legado);
  • ToDateTimeOffset: converte para DateTimeOffset, um DateTime com identificador em relação ao UTC.

Segue um código de aplicação dos métodos mais relevantes da classe XmlConvert (DecodeName, EncodeName e EncodeLocalName):

Um último ponto desse objetivo como um todo é que você pode usar diferentes classes do .NET Framework para lidar com o mesmo XML. Você pode juntar implementações onde julgar necessário; usar uma metodologia não te força a usá-la até o fim.

Esse foi o último objetivo do primeiro grande tópico da certificação, Accessing Data. O próximo post será sobre o primeiro objetivo do segundo tópico da certificação, Querying and manipulating data by using the Entity Framework.

Obrigado pela leitura e até mais!

Leia Mais

Certificação Microsoft 70-487: Objetivo 1.5 – Create and implement a WCF Data Services service

A discussão sobre WCF Data Services no objetivo 1.1 foi curta porque há um objetivo específico para ele, o Create and implement a WCF Data Services service, que será coberto nesse post.

Esse objetivo cobre como endereçar recursos, criar uma query, acessar formatos de carga (incluindo JSON) e trabalhar com interceptores e operadores de serviço. Vamos lá?

Endereçando Recursos

Para criar um WCF Data Service, basicamente você deve seguir os seguintes passos:

  • Criar uma aplicação ASP.NET (para hostear o Data Service);
  • Usar o EF para criar seu EntityModel;
  • Adicionar um WCF Data Service;
  • Especificar um tipo para o serviço (que é o nome do model container do projeto do EF);
  • Habilitar acesso ao Data Service configurando explicitamente algumas propriedades do DataServiceConfiguration (SetEntitySetAccessRule, SetServiceOperationAccessRule e DataServiceBehavior)

Quando você adicionar um WCF Data Service, você verá classes geradas assim: public class ExamSampleService : DataService. Essas classes herdam de System.Data. Services.DataService, que recebem um tipo genérico. O código gerado terá também o método InitializeService com alguns comentários que ajudam na sua configuração:

// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config) {
// TODO: set rules to indicate which entity sets and service operations are visible, updatable, etc.
// Examples:
// config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead);
// config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All); config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}

Como já citado, é importante se familiarizar com os métodos SetEntitySetAccessRule e SetServiceOperationAccessRule e a propriedade DataServiceBehavior.MaxProtocolVersion.

SetEntitySetAccessRule

Esse método recebe dois parâmetros: uma string com o nome do EntitySet e um enum EntitySetRights. Como o enum é decorado com Flags, você pode passar mais de um valor dessa forma: config.SetEntitySetAccessRule("Courses", EntitySetRights.AllRead | EntitySetRights.WriteMerge);. Os possíveis valores desse enum são:

  • None: todos os acessos são revogados;
  • ReadSingle: um item pode ser lido;
  • ReadMultiple: sets de dados podem ser lidos;
  • WriteAppend: novos itens podem ser adicionados;
  • WriteReplace: dados podem ser atualizados;
  • WriteDelete: dados podem ser deletados;
  • WriteMerge: dados podem ser mergeados;
  • AllRead: tudo pode ser lido;
  • AllWrite: tudo pode ser escrito;
  • All: todas as operações são permitidas.

SetServiceOperationAccessRule

Esse método dá permissões para uma ServiceOperation e funciona da mesma forma que o método SetEntitySetAccessRule, mas o enum é um ServiceOperationRights. Os valores desse enum são:

  • None: todos os acessos são revogados;
  • ReadSingle: um item pode ser lido;
  • ReadMultiple: sets de dados podem ser lidos;
  • AllRead: um e vários itens podem ser lidos;
  • All: todos os direitos são garantidos;
  • OverrideEntitySetRights: indica se sobrescreve os valores configurados em SetEntitySetAccessRule.

DataServiceBehavior.MaxProtocolVersion

Essa propriedade indica qual versão do protocolo OData será usado no serviço. Os possíveis valores do enum DataServiceProtocolVersion são V1 e V2.

Criando Uma Query

Uma das principais vantagens do WCF Data Services é que seus recursos são endereçáveis por URIs via OData, e você pode fazer queries nas URIs de uma forma bem simples.

Suponha que você criou um WCF Data Service com o nome ExamPrepService baseado num Entity Model que tem tópicos, questões e respostas como entidades.

Para obter todos os registros de uma entidade, a query Service/EntitySetName pode ser usada. Para obter todos os tópicos do serviço, por exemplo, a seguinte query pode ser usada:
http://servicehost/ExamPrepService.svc/Topics

A query Service/EntitySetName(‘KeyValue’) retorna a entidade que tem uma key com o valor passado. Por exemplo, para obter o tópico que tem o nome First, a query fica http://servicehost/ExamPrepService.svc/Topics('First').

Se você precisa somente da propriedade Description, a query fica http://servicehost/ExamPrepService.svc/Topics('First')/Description. Se quiser somente o valor primitivo e não o XML, basta adicionar /$value no final: http://servicehost/ExamPrepService.svc/Topics('First')/Description/$value.

As queries funcionam bem com relacionamentos, se esses estiverem definidos no Entity Model. Para obter todas as Answers da Question com a key 1, por exemplo, a seguinte query deve ser usada:
http://servicehost/ExamPrepService.svc/Questions('1')/Answers

O caminho inverso também é possível: http://servicehost/ExamPrepService.svc/Answers('1:4')/Question.

Até agora, só filtramos os resultados pela key da entidade. Mas o protocolo OData e o WCF Data Service oferecem várias opções de query que se traduzem em queries SQL. É bem importante entender o efeito de todos eles no resultado.

  • $orderby: define uma ordem para o resultado. Uma ou mais propriedades podem ser usadas, separadas por vírgula: /Questions?$orderby=Id,Description;
  • $top: define quantos resultados serão retornados: /Questions?$top=5
  • $skip: indica quantos registros devem ser ignorados. Suponha que há 30 tópicos, mas você quer ignorar os 10 primeiros e os 10 últimos. A query fica assim: /Questions?$skip=10&$top=10;
  • $filter: especifica uma ou mais condições: /Questions?$filter=Id gt 5;
  • $expand: indica quais entidades relacionadas serão retornadas: /Questions?$expand=Answers;
  • $select: por padrão, todas as propriedades de uma entidade são retornadas. Usando $select, você pode especificar quais propriedades serão retornadas separando por vírgula: /Questions&$select=Id,Text,Description,Author;
  • $inlinecount: retorna o número de registros retornados no elemento <count>: /Questions?$inlinecount=allpages.

Você também pode executar queries por meio de código usando a classe DataServiceQuery:

String ServiceUri = "http://servicehost/ExamPrepService.svc";
ExamServiceContext ExamContext = new ExamServiceContext(new Uri(ServiceUri); DataServiceQuery = ExamContext.Question
.AddQueryOptions("$filter", "id gt 5")
.AddQueryOptions("$expand", "Answers");

Acessando Formatos De Carga

o WCF Data Service dá suporte aos formatos Atom e JSON de forma fácil.

Basta usar a opção $format na query com o valor atom ou json. Caso esteja consultando via WebClient ou queira especificar isso em um request header, funciona da mesma maneira. Para JSON, coloque o header Content-Type para application/json. Para Atom, application/atom+xml.

Trabalhando Com Interceptor e Service Operator

O WCF Data Service permite que você intercepte um request para prover uma lógica customizada.

Há dois tipos de Interceptors: ChangeInterceptor, que serve para interceptar operações NON-Query, e QueryInterceptor, que intercepta operações de Query.

ChangeInterceptors têm retorno nulo e aceitam dois parâmetros: o tipo da entidade e um UpdateOperations, que referencia o request. A definição de um método ChangeInterceptor fica assim:

[ChangeInterceptor("Topics")]
public void OnChangeTopics(Topic topic, UpdateOperations operations)

QueryInterceptors são decorados com QueryInterceptor e retornam uma Expression>, que servem como filtro para a entidade. A assinatura fica assim:

[QueryInterceptor("Topics")]
public Expression> FilterTopics(){}

A dica para o exame é que, para identificar se um método é ChangeInterceptor ou QueryInterceptor, basta olhar para o tipo do retorno.
Para saber qual Interceptor deve ser implementado para fazer alguma coisa, é necessário saber o comportamento e determinar se é uma operação NON-Query ou Query. Por exemplo, DeleteTopics seria um ChangeInterceptor; já um método GetAdvancedTopics seria um QueryInterceptor.

Qualquer dúvida, fique à vontade para fazê-la nos comentários.
Fique ligado para o post do próximo objetivo, Manipulate XML data structures.

Até mais!

Leia Mais