Este artigo foi traduzido por máquina.

Pontos de dados

Usando o Silverlight 2 com serviços de dados ADO.NET

John Papa

Download do código disponível na Galeria de código do MSDN
Procure o código on-line

Conteúdo

As comunicações entre domínios
Sendo inteligente sobre dados confidenciais
Introdução rapidamente ao EDM
Referência de serviços de dados ADO.NET do Silverlight
Recuperando dados usando o LINQ
Adiada carregando
Salvando dados
Recuperando dados antecipadamente
Para ser continuado

Serviços de dados ADO.NET facilita bastante para expor dados e permitir atualizações sobre HTTP usando suas habilidades de RESTful (representações estado transferência). Os aplicativos do Silverlight podem aproveitar dos serviços de dados do ADO.NET para enviar e receber dados usando os URIs exclusivo que mapeiam para entidades. Você também pode usar consultas LINQ no cliente do Silverlight para interagir com as entidades no servidor usando a biblioteca de cliente do Silverlight de serviços de dados do ADO.NET.

Serviços de dados ADO.NET e o Silverlight tornam uma combinação poderosa, mas para torná-los funcionar bem em conjunto você precisa uma boa compreensão de alguns aspectos que podem não ser imediatamente óbvias. Então, aqui eu será endereço algumas etapas para garantir uma experiência mais bem-sucedida ao criar aplicativos com serviços de dados ADO.NET e o Silverlight.

As comunicações entre domínios

Serviços de dados ADO.NET não oferece atualmente suporte comunicações entre domínios. Há suporte para comunicações entre domínios com serviços REST e SOAP padrão, mas não com serviços de dados ADO.NET. (Como uma observação: a equipe de serviços de dados está explorando o espaço e publicará seu progresso parao blog da equipe Astoriacomo eles vá.) Isso significa que um cliente do Silverlight 2 não pode conversar com serviços expostos pelos serviços de dados do ADO.NET se esses serviços são hospedados em um domínio diferente o domínio que hospeda o aplicativo de cliente do Silverlight. Para obter mais informações sobre diretivas de domínio cruzado, consulte meuColuna de pontos de dados de 2008 de setembro. Essa coluna, aborde os formatos de arquivo e como as diretivas funcionam.

Sendo inteligente sobre dados confidenciais

Quando você estiver usando as comunicações do HTTP, todos os valores enviados por meio de URIs são claramente visíveis em uma variedade de ferramentas de detecção de rede. Uma maneira para combater isso é usar SSL para criptografar todas as comunicações do HTTP. Além disso, enviar não dados confidenciais, como números de previdência social e quaisquer outros dados particulares, no URI de. Ele é uma boa idéia nunca use dados confidenciais como identificadores. Antes de escolher qualquer comunicação RESTful, certifique-se de usar um identificador sem sentido, como um GUID, um número ou um valor IDENTITY. Por exemplo, o exemplo a seguir URI foi possível recuperar dados de funcionários usando a identificação do funcionário de 11:

http://[YourDomainHere]/MyService.svc/Employee(11)

O número de 11 é transparente no URI, por isso, ele é importante não utilizar informações confidenciais. Felizmente, nesse caso o número 11 representa um identificador fabricated, portanto, nenhum dado pessoal é exposto.

Introdução rapidamente ao EDM

Talvez a maneira de obter até mais simples e executado com serviços de dados ADO.NET seja para expor dados de um banco de dados relacional como um modelo de entidade lógica usando a estrutura de entidades ADO.NET. EDM a estrutura de entidades (modelo de dados da entidade) está totalmente ciente de como Permitir leitura e atualizar o acesso para suas entidades. Essa capacidade interna do EDM permite que ele trabalha em conjunto com serviços de dados ADO.NET com muito pouca configuração.

A primeira etapa para expor um EDM criado com a estrutura de entidades é criar os serviços de dados do ADO.NET da caixa de diálogo de modelos Visual Studio. Isso cria um modelo que pode ser facilmente modificado para expor um EDM do Framework entidade. O construtor da classe herda o DataService <t> classe de base. O T representa a classe de fonte de dados, que nesse caso é a classe de fonte de dados de estrutura de entidades chamado entidades. A classe de fonte de dados, também conhecido como a classe de contexto de objeto na estrutura de entidades, é o que permite acesso ao EDM para recuperar e salvar dados. Como a classe de fonte de dados é criada automaticamente com a estrutura de entidades, criar um serviço de dados do ADO.NET que expõe o EDM é bastante simples. Na Figura 1 , o código mostra o serviço classe NWDataService herdam o DataService <entities>.

Figura 1 Criando o DataService

public class NWDataService : DataService<Entities>
//public class NWDataService: DataService< /* TODO: put your data source 
//class name here */ >
 {
    // This method is called only once to initialize service-wide 
    //policies.
    public static void InitializeService(IDataServiceConfiguration 
        config)
    {
        //set rules to indicate which entity sets and service operations 
        //are visible, updatable, etc.
        config.SetEntitySetAccessRule("ProductSet", EntitySetRights.All);
        config.SetEntitySetAccessRule("CategorySet", 
            EntitySetRights.AllRead);
        config.SetEntitySetAccessRule("SupplierSet", 
            EntitySetRights.AllRead);
        config.SetEntitySetAccessRule("OrderSet", 
            EntitySetRights.AllRead);
        config.SetEntitySetAccessRule("OrderDetailSet", 
            EntitySetRights.All);
        config.SetEntitySetAccessRule("CustomerSet", 
            EntitySetRights.AllRead);
        /// The rest of the entity sets are not accessible.
        /// Therefore, no proxy classes are created for them either.
    }
}

A próxima etapa será para permitir ou negar leia e acesso de gravação para cada entidade define no EDM, como mostrado na Figura 1 . Isso pode ser feito em todos os conjuntos de entidade usando o * ou em uma entidade individual definir nível especificando cada nome de conjunto de entidade. O método SetEntitySetAccessRule aceita o nome do conjunto de entidade que a regra será aplicada a e um ou mais valores de enum EntitySetRights. Observe na Figura 2 que cada um da entidade define para ProductSet OrderSet OrderDetailSet, CustomerSet, SupplierSet e CategorySet permitir acesso completo. Isso significa que os conjuntos de entidade seis permitir que a leitura, inserir, atualizar e excluir. Esses são configurações de acesso ao nível de serviço e aplicam a todas as solicitações que chegam ao sistema. Qualquer conjunto de entidade não listado, como EmployeeSet ou RegionSet, não está acessível.

A Figura 2 System.Data.Services EntitySetRights enums
Enum Descrição
Todos os Todas as leituras e gravações são permitidas na entidade especificada
AllRead Todas as leituras são permitidas
AllWrite Todos os gravar operações são permitidas
Nenhum Não há acesso tem permissão para a entidade especificada
ReadMultiple Várias linhas de leitura é permitido
ReadSingle Ler uma única linha é permitido
WriteAppend Criar novos dados é permitida
WriteDelete Exclusão de dados é permitida
WriteMerge Com a mesclagem atualizações são permitidas
WriteReplace Substituindo é permitido

Os valores de enum EntitySetRights indicam o tipo de acesso permitido para o conjunto de entidade. a Figura 2 mostra todos os valores de enum válido e suas descrições. Permissão pode ser dada em uma escala global para todos os conjuntos de entidade, muito. Por exemplo, a seguinte linha de código permitiria que todos os acesso de leitura à entidade todas as define, mas não permitiria o acesso de gravação:

config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);

Direitos podem ser combinados para permitir que mais de um direito para um conjunto de entidade, também. O código a seguir torna a entidade ProductSet definir acessíveis para ler e atualizar somente:

config.SetEntitySetAccessRule("ProductSet", 
EntitySetRights.AllRead | 
EntitySetRights.WriteMerge | 
EntitySetRights.WriteReplace);

Execute as seguintes etapas exporá os conjuntos de entidade seis (mostrados na Figura 1 ) e torná-los disponíveis por meio de um serviço de dados do ADO.NET. Outros conjuntos de entidade e as permissões podem ser personalizadas também.

Modelos de entidade criados com s outros mapeamentos de relacional do objeto (ORM), como LINQ to SQL e NHibernate, também podem ser usados com serviços de dados do ADO.NET. Os objetos de contexto devem implementar IQueryable para cada conjunto de entidade que irá ser consultado. Para criação, atualização e as operações de exclusão, o objeto de contexto deve implementar a interface IUpdatable. Serviços de dados ADO.NET tem conhecimento interno da estrutura de entidades ADO.NET que permite oferecer suporte a consulta e atualização por meio serviços de dados do ADO.NET. É provável que versões futuras do outros ORMs também possuem suporte para serviços de dados ADO.NET ou pelo menos ter classes auxiliares para ajudar a familiarizar. Mas por ora, se você desejar usar um ORM diferente de estrutura de entidades com serviços de dados do ADO.NET, você precisará implementam as interfaces descritas acima. Na verdade, serviços de dados ADO.NET não é específico de dados relacionais. Qualquer fonte de dados pode ser configurado por meio de técnicas observadas neste parágrafo.

fig03.gif

A Figura 3 gerado classes de referência de serviço

Referência de serviços de dados ADO.NET do Silverlight

Depois que o serviço de dados ADO.NET tiver sido criado e interno, um aplicativo do Silverlight pode referenciar o serviço e interagir com ele. Um serviço de dados do ADO.NET pode ser referenciado do aplicativo Silverlight pela clicando com botão direito do mouse o nó de referências de serviço no Solution Explorer para abrir a janela de diálogo Add Service Reference. Ou você pode inserir o URI do serviço na caixa Endereço ou você pode clicar no botão de descobrir e clique no botão Ir para localizar o serviço. Depois que os metadados para o serviço é recuperado, o serviço e os conjuntos de entidades que ele expõe serão exibidos em uma lista. Finalmente, nomeie a referência de serviço e clique no botão OK.

Isso cria uma classe proxy no cliente Silverlight que permite que você interagir com o serviço de dados do ADO.NET. Se você clique no botão Mostrar todos os arquivos na janela Solution Explorer e expanda o nó NWServiceReference completamente, você verá um arquivo Reference.cs. Esse arquivo contém a classe de proxy gerado que permite a interação com o serviço de dados ADO.NET e classes geradas para as entidades que são expostas por meio do serviço. Lista de classes também pode ser vista por procurando na janela Class View, conforme mostrado na Figura 3 .

Observe que todas as entidades no modelo a estrutura de entidades no projeto da Web não são listadas no modo de exibição de classe. Somente conjuntos de entidade que podem ser acessados por meio do serviço de dados terão classes de proxy geradas para eles quando uma referência de serviço é adicionada ao serviço de dados ADO.NET de um cliente do Silverlight. Os conjuntos de seis entidade mostrados no modo de exibição de classe fossem expostos usando os métodos de SetEntitySetAccessRule no serviço de dados ADO.NET, portanto, esses são os conjuntos de entidade única disponíveis para o cliente do Silverlight. A classe sétima na janela Class View, a classe de entidades, é uma classe proxy que representa o serviço de dados como um todo e facilita as chamadas para NWDataService.svc.

Recuperando dados usando o LINQ

A próxima etapa é fazer referência o assembly System.Data.Services.Client no projeto do Silverlight (consulte Figura 4 ). Neste módulo (assembly) torna mais fácil interagir com o serviço de dados do ADO.NET usando LINQ. Por exemplo, o código a seguir cria uma consulta LINQ que seleciona todos os produtos usando serviços de dados do ADO.NET:

  _products.Clear();
  DataServiceQuery<Product> dq = (from p in _ctx.ProductSet select p)
  as
  DataServiceQuery<Product>;
  dq.BeginExecute(new AsyncCallback(FindProduct_Completed), dq);

fig04.gif

A Figura 4 referenciar o ClientLibrary de serviços de dados ADO.NET do Silverlight

A consulta LINQ nesse código é convertida em um URI que serviços de dados ADO.NET pode ler. A consulta é convertida em um DataServiceQuery <t>, que faz parte do namespace System.Data.Services.Client. A consulta é executada de forma assíncrona, para um método de retorno de chamada válido deve ser especificado para receber os resultados da consulta. Quando executa a consulta no exemplo de código anterior e os dados são retornados, o método FindProduct_Completed será chamado.

O método de FindProduct_Completed (mostrado na Figura 5 ) aceita um parâmetro de IAsyncResult contendo os resultados da consulta. Os resultados são lidas, chamando o método EndExecute, que gera um conjunto de objetos do. Cada produto, em seguida, é examinado e um manipulador de eventos é atribuído para o evento PropertyChanged. (No código de exemplo, eu estendida a classe de produto usando uma classe parcial para adicionar a implementação de INotifyPropertyChanged.) Esta etapa garante que quando alterações são feitas para qualquer instância de produto no Silverlight, que o produto notificará o DataServiceContext (entidades, na Figura 5 ), chamando o método UpdateObject. Sem esse código, DatServiceContext seria sabem das alterações feitas pelo usuário a uma instância de produto. <product>Supondo que a variável _products é do tipo ObservableCollection <produto> e que ele está vinculado ao DataContext de um controle DataGrid, os produtos serão exibidos no controle DataGrid (consulte a a Figura 6 ).

A Figura 5 FindProduct_Completed método

private void FindProduct_Completed(IAsyncResult result)
{
    DataServiceQuery<Product> query = (DataServiceQuery<Product>)result.
         AsyncState;
    try
    {
        var entities = query.EndExecute(result);
        foreach (Product item in entities)
        {
            item.PropertyChanged += ((sender, e) =>
                                         {
                                             Product entity = (Product)
                                                                sender;
                                             _ctx.UpdateObject(entity);
                                         });
            _products.Add(item);
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Failed to retrieve data: " + ex.ToString());
    }
}

fig06.gif

A Figura 6 carregamento de produtos e detalhes do pedido

Adiada carregando

A biblioteca de cliente de serviços de dados ADO.NET no Silverlight oferece a capacidade de carregar objetos associados a um objeto já estão na DataServiceContext. Por exemplo, quando um produto é selecionado em DataGrid superior no controle do Silverlight mostrado na Figura 6 , antes dos detalhes da ordem do produto podem ser vinculados a DataGrid inferior, devem ser recuperadas. A primeira etapa nesse processo é atribuir um manipulador de eventos para o evento SelectionChanged do productDataGrid. Em seguida, quando um produto é selecionado, o manipulador de eventos (mostrado na Figura 7 ) usa o método BeginLoadProperty para solicitar serviços de dados ADO.NET para obter os objetos OrderDetails para o produto selecionado.

A Figura 7 solicitando detalhes do pedido

void productDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    Product product = productDataGrid.SelectedItem as Product;
    if (product == null) return;

    _orderDetails.Clear();

    if (product.OrderDetails == null || product.OrderDetails.Count == 0)
    {
        _ctx.BeginLoadProperty(product, "OrderDetails", FindOrderDetail_Completed, null);
    }
    else
    {
        LoadOrderDetails();
    }
}

O método BeginLoadProperty faz uma solicitação de rede para obter os registros de OrderDetails e quando ela retorna ele irá chamar o método de retorno de chamada, FindOrderDetail_Completed. Se qualquer estado adicional precisa ser passadas deste código para o método de retorno de chamada, ele pode ser passados no quarto parâmetro do método BeginLoadProperty. Por exemplo, o mesmo manipulador de eventos pode ser usado para receber os resultados de várias consultas assíncronas. Um valor pode ser transmitido no parâmetro estado para ajudar a determinar cujos resultados de consulta chamada assíncrona que ele está recebendo o método de retorno de chamada.

Quando o retorno de chamada é chamado, os resultados são lidos no objeto DataServiceContext usando seu método EndLoadProperty, conforme mostrado aqui:

_ctx.EndLoadProperty(result);
Deployment.Current.Dispatcher.BeginInvoke(() => LoadOrderDetails());

Os detalhes da ordem, em seguida, são carregados pelo método LoadOrderDetails. Como o código executa como resultado de uma operação assíncrona concluir, não há nenhuma garantia de que esse código será executado no thread da interface do usuário.

Se o código não está sendo executado no thread da interface do usuário, em seguida, o novo conjunto de detalhes da ordem não aparecerá no elemento DataGrid. Basicamente, qualquer operação de interface do usuário deve executada no thread da interface do usuário. Uma maneira de verificar que uma operação é executado no segmento de interface do usuário é usar o Dispatcher método do objeto BeginInvoke. O código anterior usa o Dispatcher para verificar que os detalhes da ordem são carregados no thread da interface do usuário chamando LoadOrderDetails (mostrado na Figura 8 ).

Detalhes do pedido ao carregar a Figura 8

private void LoadOrderDetails()
{
    Product product = productDataGrid.SelectedItem as Product;
    if (product == null) return;

    var query = (from od in product.OrderDetails
                 orderby od.OrderID ascending
                 select od);
    foreach (OrderDetail item in query)
    {
        item.PropertyChanged += ((sender, e) =>
        {
            OrderDetail entity = (OrderDetail)sender;
            _ctx.UpdateObject(entity);
        });
        _orderDetails.Add(item);
    }
}

LoadOrderDetails captura a instância de produto selecionada no momento e cria uma consulta LINQ que irá selecionar todos os objetos OrderDetails o produto selecionado. Isso é possível, pois os detalhes da ordem foram carregados DataServiceContext usando EndLoadProperty. Depois que os detalhes da ordem são recuperados usando a consulta LINQ na Figura 8 , cada instância de objeto de OrderDetail tem seu PropertyChanged manipulador de eventos atribuído uma expressão lambda que informa DataServiceContext se quaisquer valores de propriedade serão alteradas. Supondo que o _orderDetails objeto é uma ObservableCollection <orderdetail> elemento que será vinculado à orderDetailsDataGrid, os detalhes da ordem aparecerá (como você viu na a Figura 6 ).

Salvando dados

O exemplo mostrado também permite que o usuário salvar alterações em ambos os produtos e os detalhes da ordem. Há duas etapas básicas para permitir salvar as alterações: notificar DataServiceContext quando ocorrerem alterações e emissão de salvamento operação assincronamente.

Esses conjuntos hierárquicos de dados cada tem uma classe parcial no Silverlight que se estende as entidades de produto e OrderDetail. A classe parcial para o produto (mostrado na Figura 9 ) implementa a interface INotifyPropertyChanged, que requer que o evento PropertyChanged sejam implementados. A classe parcial do gerada por criar a referência de serviço para o serviço de dados do ADO.NET cria os métodos parciais para cada uma as propriedades públicas na classe. Cada propriedade obtém um método que é acionado quando uma propriedade é sobre a alteração e um quando a propriedade já foi alterada. Por exemplo, a produto propriedade da classe ProductName possui um método parcial OnProductNameChanging e um método parcial OnProductNameChanged. a Figura 9 mostra que o método de parcial de OnProductNameChanged no código personalizado (não a classe gerada) aciona o evento PropertyChanged. Esta é a chave para controlar todas as alterações a valores de propriedade todas as entidades. DataServiceContext precisa saber quando alteram os valores de propriedade e o que eles alterar para; caso contrário, ele não é possível salvar as alterações.

A Figura 9 Implementando notificação de alteração

public partial class Product :INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private void FirePropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }

    partial void OnProductIDChanged() { FirePropertyChanged("ProductID"); }
    partial void OnProductNameChanged() { FirePropertyChanged("ProductName"); }
    partial void OnDiscontinuedChanged() { FirePropertyChanged("Discontinued"); }
    partial void OnDiscontinuedDateChanged() { FirePropertyChanged("DiscontinuedDate"); }
    partial void OnQuantityPerUnitChanged() { FirePropertyChanged("QuantityPerUnit"); }
    partial void OnReorderLevelChanged() { FirePropertyChanged("ReorderLevel"); }
    partial void OnRowVersionStampChanged() { FirePropertyChanged("RowVersionStamp"); }
    partial void OnUnitPriceChanged() { FirePropertyChanged("UnitPrice"); }
    partial void OnUnitsInStockChanged() { FirePropertyChanged("UnitsInStock"); }
    partial void OnUnitsOnOrderChanged() { FirePropertyChanged("UnitsOnOrder"); }
}

Depois que a propriedade alterada manipuladores de eventos são definidas para cada propriedade, DataServiceContext irá estar ciente das alterações e o restante do trabalho para salvar os dados é relativamente simples. Quando um usuário clica no botão Salvar o método de BeginSaveChanges é chamado em DataServiceContext, conforme mostrado aqui:

private void SaveButton_Clicked(object sender, RoutedEventArgs e)
{
    _ctx.BeginSaveChanges(SaveChangesOptions.Batch, 
        new AsyncCallback(Save_Complete), null);
}

Esse método faz uma operação POST pelos serviços de dados do ADO.NET, enviando todas as alterações que DataServiceContext está ciente do. O POST é emitido assincronamente, portanto, um método de retorno de chamada é necessário para processar os resultados. O parâmetro SaveChangesOption.Batch indica que todas as alterações devem ser salvos em uma transação no modo de lotes. Se houver falhas de operações de salvamento, eles serão todos os falhar e a transação será revertida. Essa técnica funciona se você estiver salvando um único registro de uma entidade ou vários registros de várias entidades associadas.

Recuperando dados antecipadamente

Anteriormente eu abordou como executar adiada carregar usando o método BeginLoadProperty de DataServiceContext para capturar os registros para uma entidade existente e anexá-los a DataServiceContext. Essa técnica é ótimo para obter dados adicionais sob demanda, especialmente quando os dados não é sempre necessário. Carregamento adiado salva o custo da recuperação de dados que o usuário pode não desejar ver, a menos que o usuário solicita os dados (o que acontece quando o usuário seleciona o produto no controle productDataGrid).

Outra técnica para recuperar dados hierárquicos é para pedir-completo antecipadamente. Por exemplo, ao recuperar registros de produto pode ser útil também obter categoria de cada produto e de fornecedor. Caso contrário, as propriedades de categoria e o fornecedor de uma instância de produto estará nulas. A técnica de BeginLoadProperty pode ser usada, mas que exigiria fazendo várias solicitações de rede. Uma melhor técnica para obter todos os dados ao mesmo tempo é usar o método de expandir na consulta LINQ, desde que ele exigiria apenas uma solicitação HTTP. A consulta a seguir mostra o método de expandir solicitando todas as categorias e fornecedores para os produtos selecionados na consulta LINQ.

   DataServiceQuery<Product> dq = (
    from p in _ctx.ProductSet.Expand("Categories").Expand("Suppliers")
    select p) 
    as DataServiceQuery<Product>;

O método de expandir tem um custo como ele recupera dados adicionais. Isso deve ser usado somente quando é necessário capturar todos os dados antecipadamente. O método de expansão mostrado no código anterior captura registros filho para cada entidade de produto. Em outras palavras, a categoria e de fornecedor são ambos os filhos diretos de um produto. Observe que o nome da propriedade, não o nome do conjunto entidade, é passado para o método de expandir.

Se forem necessários vários níveis de uma hierarquia, a sintaxe do método de expandir pode ser adaptada para obter os registros muito. Por exemplo, se desejar capturar o elemento OrderDetail para cada produto bem como cada um dos pedidos desses OrderDetail, a sintaxe pode parecer isso:

DataServiceQuery<Product> dq = (
    from p in _ctx.ProductSet.Expand("OrderDetails/Orders")
    select p) 
    as DataServiceQuery<Product>;

Esse código indica que a consulta deve obter as entidades para a propriedade OrderDetails de cada produto bem como cada entidade para os pedidos. (A classe Product tem uma propriedade OrderDetails e a classe OrderDetail tem uma propriedade de pedidos). Os registros de OrderDetail são implícita, pois eles são necessários ao obter a propriedade de pedidos para cada produto. Essa consulta retornará sobre 8MB dos dados XML para o aplicativo de cliente do Silverlight. Essa é uma grande quantidade de dados e pode afetar negativamente o desempenho em conexões mais lentas. Eu recomendável usando o método de expandir somente quando os dados são necessários e mesmo nesse caso, usá-lo com o filtro mais restritivo possível para evitar a recuperação de dados que não é necessário.

Para ser continuado

Há exatamente um pouco de funcionalidade exposta pela combinação de serviços de dados ADO.NET e o Silverlight que tornam um aplicativo orientado a dados robusto. Uma edição futura de pontos de dados, espero Revise o tópico e oferecer mais dicas. Por enquanto, check-out de meu blog:johnpapa. NET, o Silverlight. site NET, e anteriores prestações de pontos de dadosPara obter mais no Silverlight centrado em dados aplicativos.

Envie suas dúvidas e comentários para John a mmdata@Microsoft.com.

John Papa  (johnpapa. NET) é um consultor sênior com ASPSOFT e um fã de beisebol que gasta noites de verão raiz pelos Yankees com sua família. John, um MVP C#, Insider do Silverlight e INETA orador, é autor de vários livros, incluindo suas mais recentes intitulado Data-Driven Services with Silverlight 2 (o ' Reilly, 2009). Ele geralmente fala em conferências, como VSLive, DevConnections e Mix.