Este artigo foi traduzido por máquina.

Serviços de RIA .NET

Criando uma despesa baseados em dados aplicativo com o Silverlight 3

Jonathan Carter

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

Este artigo se baseia em uma versão de pré-lançamento do .NET RIA Services. Todas as informações estão sujeitas a alterações.

Este artigo discute:

  • Introdução ao .NET RIA Services
  • Serviços de dados e operações de domínio
  • Projeção de código
  • Controles de dados flexível
Este artigo usa as seguintes tecnologias:
Silverlight 3, .NET RIA Services, WPF

Conteúdo

Guia de Introdução
Biblioteca de serviços de dados
Operações de domínio
Projeção de código
Controles de dados
ObjectDataSource
DataPager
DataForm
Os metadados
Validação
Código compartilhado
Dispor para cima

no desenvolvimento de software, há são muitos estilos diferentes de aplicativos, cada um deles vem com sua própria finalidade e requisitos, pontos fortes e fracos. Ao escolher uma plataforma de desenvolvimento ou estrutura, parte do processo de decisão será provavelmente dependem se a estrutura facilmente pode habilitar o tipo de aplicativo que você está procurando para criar. Não desenvolvedor deseja gastar horas escrevendo encanamento ou código de infra-estrutura, e nenhum cliente deseja pagar por esse tipo de trabalho a ser feito. Poder principalmente se concentrar em necessidades de negócios é importante, portanto, ter uma plataforma de desenvolvimento que melhor permite que o foco e aumenta a produtividade é que um absoluto necessário.

Silverlight é uma excelente tecnologia para criar aplicativos Web atraentes. Embora o Silverlight 2.0 inclui um subconjunto do Windows Presentation Foundation (WPF), um volume razoável da funcionalidade do WPF tornou fácil criar aplicativos orientados a dados não foi herdado. Isso foi causado muitos desenvolvedores para Finalmente passar backup usando o Silverlight porque isso exigiria muito trabalho para implementar a infra-estrutura centrado em dados de soluções de Web mais sofisticadas. Como parte da versão 3 do Silverlight, novos controles e recursos são sendo introduzidos que facilitam aplicativos controlados por dados especificamente desenvolver. Isso inclui novos controles de dados, navegação, validação e incorporado janelas de diálogo.

Enquanto esses aperfeiçoamentos do lado do cliente fornecem uma quantidade considerável de valor em seu próprios, o Silverlight é um ambiente multicamadas e assim requer conhecimento de como lidar com comunicação cliente servidor. Para resolver esse problema, serviços de RIA .NET (codinome " Alexandria") fornece um conjunto de componentes de servidor e extensões do ASP.NET que facilitam o processo de desenvolvimento de n camadas, fazer seus aplicativos quase tão fácil desenvolver como se eles estivessem sendo executados em uma única camada. Além disso, serviços, como autenticação, funções e gerenciamento de perfis são fornecidos. A combinação de cliente e servidor aprimoramentos 3 do Silverlight e ASP.NET, junto com a adição do .NET RIA Services, simplificar a experiência de ponta a ponta de desenvolver aplicativos de Web orientado a dados, também conhecido como aplicativos de Internet rich, ou RIAs.

fig01.gif

Figura 1 padrão .NET RIA serviços projeto

Neste artigo, mostrarei como as melhorias ao Silverlight 3, em conjunto com a funcionalidade introduzido pelo .NET RIA Services, fornecer um rico ambiente para desenvolver aplicativos de Web centrado em dados. Para ilustrar melhor a funcionalidade que 3 do Silverlight e serviços de RIA .NET fornecem, vou criar um relatório de despesas rastreamento de aplicativo que será aproveitar os novos conjuntos de recurso. O aplicativo permitirá que os funcionários a fazer logon e gerenciar e criar os relatórios de despesas de pessoal. Quando um relatório de despesas for concluído, ele possível, em seguida, enviar, aguardando a aprovação do gerente.

Guia de Introdução

Depois da instalação do .NET RIA Services e 3 do Silverlight, o Visual Studio inclui um novo modelo de projeto chamado Silverlight aplicativo de dados que lhe permite colocar e executar rapidamente. Quando a solução é criada, você está recebe dois projetos: um projeto do Silverlight e um ASP.NET projeto (veja a Figura 1 ). O projeto do Silverlight representa o aplicativo cliente e o projeto ASP.NET irá conter a lógica de back-end.

O que é a vantagem dessa estrutura de projeto padrão? Longe como o Silverlight fornecido projeto entra, ele é idêntico ao que você poderia obter se você criou um projeto do Silverlight regular. Você tem um arquivo App.xaml padrão e uma página padrão. Existem, entretanto, diferenças sutis para este projeto do Silverlight que irá ajudar suas metas de desenvolvimento.

O projeto ASP.NET é bem direto, mas se você olhar dentro da página default.aspx, você observará que faz uso de um novo controle de servidor chamado SilverlightApplication.

<ria:SilverlightApplication 
  ID="Xaml1" runat="server" 
  Source="~/ClientBin/ExpenseReports.xap" 
  MinimumVersion="1.0" 
  Width="100%" Height="100%" />

Esse controle deve fazer o ato de hospedar um aplicativo do Silverlight em um aplicativo formulários da Web do ASP.NET mais fácil. O controle SilverlightApplication já está configurado para direcionar o XAP que será criado no projeto do Silverlight.

Neste ponto, você ter um aplicativo de Silverlight totalmente funcional e hospedado. Agora basta começar a adicionar lógica de negócios personalizado e interface do usuário.

Vamos supor que você já tem um banco de dados no local para este aplicativo, portanto, a próxima coisa a fazer é adicionar um modelo de dados de entidades do ADO.NET ao projeto de servidor. Estará usando a estrutura de entidades como o mapeamento relacional do objeto (O/RM) neste artigo, mas você também pode usar LINQ para SQL, NHibernate, ADO.NET tradicional ou qualquer outro método de acesso de dados.

O modelo de dados é muito simples, que contém apenas três entidades: ExpenseReport, ExpenseReportDetail e funcionários (veja a Figura 2 ). Para obter mais informações sobre o Estrutura de entidades do ADO.NET no MSDN.

fig02.gif

A Figura 2 modo de dados

Com o modelo de dados no lugar e a seleção de o/Gerenciador de recursos feita, você agora pode começar a implementar a lógica de back-end. Como este projeto emprega Silverlight como o cliente e o ASP.NET como o host, você precisará decidir sobre uma abordagem para comunicação entre as duas camadas. Você pode usar qualquer um das estruturas de comunicação disponível no momento como o WCF (Windows Communication Foundation) ou serviços de dados do ADO.NET, dependendo dos seus requisitos.

Biblioteca de serviços de dados

Serviços de RIA .NET apresenta uma biblioteca de componentes de servidor que constituem a construção orientado a dados RIAs mais fácil. A biblioteca não é dependente a qualquer estrutura de interface do usuário específica, e embora este artigo se concentre no seu consumo pelo Silverlight, que é apenas uma das opções possíveis clientes. No futuro versões, serviços de RIA .NET também funcionarão com serviços de dados do ADO.NET.

.NET RIA Services principalmente gira em torno de uma nova classe chamada DomainService que atua como uma empresa de servidor para interação de modelo de lógica e os dados comerciais. Criação de um é tão simples como aproveitar os modelos de classe de lógica comercial instalados pelo .NET RIA Services no Visual Studio.

Um DomainService pode existir em duas formas: dados model–specific ou genérico. A implementação de model–specific dados efetivamente envolve um modelo de dados, permitindo que você a opção de escrever uma combinação de código de acesso lógica e os dados comerciais. Isso torna mais conveniente fazer backup e executar rapidamente em cenários em que você tiver um modelo de dados que você deseja trabalhar.

Existem duas implementações de DomainService de model–specific de dados fornecidas com o .NET RIA Services: Estrutura de Entidades do ADO.NET e LINQ para SQL. Se seu aplicativo faz uso de um daqueles, e você pode selecione a opção apropriada no assistente ao criar um DomainService. Se você estiver usando outro tipo de modelo de dados ou o/Gerenciador de recursos, você pode criar sua própria implementação específica de um DomainService bem.

Neste artigo, como eu já tem um modelo de dados de entidade no local, irá prosseguir e criar um DomainService específicos do Framework de entidade. Recebo uma nova classe que herda de LinqToEntitiesDomainService <t>:

[EnableClientAccess]
public class ExpenseService : 
  LinqToEntitiesDomainService<ObjectContext> {

  //public IEnumerable<Employee> GetEmployees()
  //{
  //    return this.Context.Employee;
  //}
}

Nesse caso, o parâmetro genérico representa o tipo de instância de ObjectContext que representa a conexão com o modelo de dados de entidade. A primeira etapa é heed o conselho do comentário TODO e substitua o espaço reservado do tipo de parâmetro a ObjectContext real, nesta ExpenseReportContext caso.

Além de herdam DomainService (ou um derivativo), um domínio do serviço pode ter um EnableClientAccessAttribute anexado a ele. Esse atributo é o que realmente significa sua classe como sendo um serviço de domínio e permite que você especifique se ele deve ser publicamente exposto. Por publicamente expostos, QUERO dizer acessível para seu aplicativo cliente. Isso lhe dá a opção para determinar se alguma lógica somente é necessário no servidor, ou se ele também deve estar disponível no cliente.

Operações de domínio

Um serviço de domínio não é muito útil em sua própria a menos que você adicione funcionalidade em forma de operações de domínio. Uma operação de domínio representa um ponto de extremidade do seu serviço de domínio e pode executar criar, ler, atualizar e excluir operações de CRUD (no seu modelo de dados, a lógica de negócios arbitrário ou ambos. Cada operação de domínio deve mapear para um tipo de operação específica, incluindo consulta, inserção, atualização, exclusão, operação de serviço e personalizado. O mapeamento de tipo de operação pode ocorrer por convenção ou pela configuração.

Dependendo do tipo operação sua operação de domínio será executar, ele precisa seguir uma assinatura específica. Além disso, algumas operações precisam seguir uma convenção de nomenclatura específica ou ter um atributo que determina que tipo de operação é. Por exemplo, se é uma operação de consulta, e seu tipo de retorno deve ser o IEnumerable <T> ou IQueryable <T> onde T é um tipo de entidade funciona com. Ele pode aceitar qualquer número de parâmetros, que pode agir como filtros, mas nenhum são necessários.

Criação de uma operação de domínio para recuperar todos os relatórios de despesas pode parecer com isso:

public IEnumerable<ExpenseReport> 
  GetExpenseReports() {

  return Context.ExpenseReports;
}

ExpenseReport é uma entidade no modelo de dados subjacente e é, portanto, um tipo de retorno válido. A classe DomainService contém uma propriedade chamada contexto que fornece acesso a uma instância do seu modelo de dados (o tipo fornecido como um parâmetro genérico), que nesse caso permite que você consultar para obter uma lista de todos os relatórios de despesas. Esta operação é mapeada para um tipo por convenção.

Criando um método de dados para recuperar um relatório de despesas específica pode parecer com isso:

[Query(IsComposable = false)]
public IEnumerable<ExpenseReport> 
  GetExpenseReport(int id) {

  var expenseReport = 
    from rep in Context.ExpenseReports
    where rep.Id == id
    select rep;

  return expenseReport;
}

Observe que esse método de dados leva um parâmetro para recuperar um relatório de despesas específica por ID. Como você tem acesso ao ObjectContext subjacente, você também pode escrever a consulta usando o LINQ. Esse método é mapeado por configuração, usando o QueryAttribute, que também permite especificar propriedades adicionais que não podem ser obtidas Convencionalmente. Irá tocar no que a propriedade de IsComposable do QueryAttribute faz mais adiante neste artigo.

Um DomainService pode conter como muitos consultar métodos, conforme necessário. Além de recuperar dados, você pode criar operações para dados persistentes volta para o modelo de dados. Implementar operações de persistência básica (inserir/atualizar/excluir) para relatórios de despesas pode parecer com isso:

public void InsertExpenseReport(
  ExpenseReport expenseReport) {
  Context.AddToExpenseReports(expenseReport);
}

public void UpdateExpenseReport(
  ExpenseReport current, ExpenseReport original) {
  Context.AttachAsModified(current, original);
}

public void DeleteExpenseReport(
  ExpenseReport expenseReport) {
  Context.DeleteObject(expenseReport);
}

Você pode definir as operações de persistência para detalhes do relatório de despesas da mesma maneira. Você pode ter apenas uma única inserir, atualizar e excluir método por tipo de entidade (ExpenseReport neste cenário) porque, quando chegar uma solicitação para o DomainService para salvar alterações o modelo de dados, o DomainService precisa saber qual método para chamar.

Uma coisa a observar sobre essas operações é suas assinaturas. Quando você cria uma inserção ou o método delete, ele deve seguir um único parâmetro que está de acordo com para o tipo de entidade que o método é responsável pela inserção ou exclusão. Quando você cria um método de atualização, ele deve levar dois parâmetros: a instância de entidade modificado (ou atual) e a instância de entidade original. A assinatura de método em conjunto com o nome do método é o que significa para o DomainService qual método é responsável por qual operação para qual tipo de entidade.

A convenção de nomenclatura que pode ser usada quando definir operações de persistência é prefixar os nomes de método com o tipo de operação. Por exemplo, porque o método é chamado DeleteExpenseReport, o prefixo de excluir significa que é uma operação de exclusão por convenção. A assinatura, em seguida, define o tipo de entidade está associado (ExpenseReport). Como você poderia esperar, o prefixo de nome de método convencional para operações de atualização é atualização e inserir operações é inserir. Esse é o motivo pelo qual eu não tenha feito nenhuma configuração adicional para essas operações trabalhar.

Se uma operação não seguir a convenção de nomenclatura esperada ou você precisa especificar metadados adicionais para a operação (como eu fiz com o método GetExpenseReport), em seguida, você pode aplicar atributos de configuração, como QueryAttribute, InsertAttribute, DeleteAttribute e UpdateAttribute como fiz com o método GetExpenseReport.

Se você precisar definir lógica comercial que não é necessariamente associada a uma operação de CRUD, mas está associado a um tipo de entidade, você pode criar uma operação de serviço. Nesse caso, eu quer operações para aprovar e rejeitar relatórios de despesas. Isso significa simplesmente modificando o valor de status o relatório de despesas (veja a Figura 3 ). A assinatura de uma operação de serviço deve aceitar uma instância do tipo entidade ele está associado e deve retornar void. Observe que há não convenção para definir operações de serviço, para que você deve aplicar o ServiceOperationAttribute para configurar qualquer operação de domínio apropriado.

A Figura 3 operações de serviço

[ServiceOperation]
public void ApproveExpenseReport(
  ExpenseReport expenseReport) {

  if (expenseReport.Status == 1) {
    if (expenseReport.EntityState == 
      System.Data.EntityState.Detached) {
      Context.Attach(expenseReport);
    }
    expenseReport.Status = 2;
  }
}

[ServiceOperation]
public void RejectExpenseReport(ExpenseReport er) {
  if (er.Status == 1) {
    if (er.EntityState == 
      System.Data.EntityState.Detached) {
       Context.Attach(er);
    }
    er.Status = 0;
  }
}

Agora que você tem o serviço de domínio e operações criadas, como você fazer interagindo com o serviço de host do servidor ASP.NET para o aplicativo cliente do Silverlight? Se você estivesse usando o WCF ou serviços de dados ADO.NET, você criaria uma referência do projeto do Silverlight que apontado para o serviço, que poderia gerar um proxy. Serviços de RIA .NET fornece uma experiência um pouco mais sofisticada.

Projeção de código

Quando você cria a solução .NET RIA Services, o arquivo de projeto do Silverlight tinha algumas tarefas MSBuild especiais que tratam de projeção componentes de servidor específico para o cliente adicionadas a ela. Quando você cria um serviço de domínio no projeto ASP.NET que tem um EnableClientAccessAttribute aplicada a ele, a tarefa do MSBuild será projeto esse serviço de domínio para o aplicativo do Silverlight. (Se você não criar sua solução usando o novo modelo de projeto de aplicativo de dados do Silverlight, você pode manualmente vincular um projeto do Silverlight e projeto do ASP.NET no Visual Studio para obter o mesmo efeito.)

Se você olhar no code-behind do arquivo Main.xaml no projeto do Silverlight, você encontrará tipos ExpenseContext e ExpenseReport. Quando um provedor de dados é projetado para um aplicativo de cliente do Silverlight, ele não está mais um DomainService (ou subtipo), mas em vez disso, um DomainContext. Na verdade, se o serviço de domínio é um sufixo formado com a palavra serviço, ele será substituído com contexto na projeção (que é por que a classe ExpenseService é refletida como contexto de despesas no projeto do Silverlight). A classe de DomainContext atua como um proxy do lado do cliente para um DomainService e contém a lógica necessária para solicitações entre as duas camadas de comunicação. Ele representa uma unidade de trabalho e pode criar uma série de conjuntos de alteração feita para qualquer instância de entidade é atualmente controle.

Além disso, certos métodos (métodos públicos que são considerados operações de domínio por convenção ou configuração) serão ser projetados juntamente com os serviços de domínio pai. Dos tipos de método dados seis, apenas três são projetados: personalizados, serviço e de consulta. Se o nome de um método de consulta é prefixado com Get, na projeção Get será substituído com carga. Por exemplo, porque eu definido um método de consulta com o nome GetExpenseReports, ele será superficial no cliente como LoadExpenseReports. As operações de dois service foram projetadas bem e sejam refletidas como métodos de instância no DomainContext.

Finalmente, os tipos de entidade são retornados de uma operação de domínio serão também ser projetados. <expensereport>No meu exemplo, porque retorno tipo o método de relatórios de GetExpense IEnumerable < ExpenseReport >, a classe de entidade ExpenseReport será ser projetada para o cliente do Silverlight. As classes de entidade projetado herdam de uma classe especial chamada entidade que fornece comportamento como controle de alterações, verificação de validação e editability do WPF, SL-compatível. Da perspectiva de API, sua classe de entidade do lado do cliente irá parecer exatamente como a entidade no servidor, mas conterá a funcionalidade adicional necessária para interagir com controles de dados do Silverlight.

Há muito mais detalhes ao redor o comportamento de projeção de código que são discutidos aqui. Este artigo não tocar em muitos cenários adicionais que são possíveis, inclusive a capacidade de personalizar a lógica que determina como código de um tipo específico é formato antes de Projetando-lo para o aplicativo cliente.

ESTOU usando a projeção de código termo porque eu acho que ele adequadamente descreve o que está acontecendo. A classe ExpenseService simplesmente não está sendo copiada de um projeto para outro (há, na verdade, uma exceção a isso que discutirei posteriormente). Ele é que está sendo examinado, com e projetado para o aplicativo cliente como um proxy fáceis de usar. Portanto, você pode utilizar o tipo da camada do cliente como se fosse um objeto local.

Onde o serviço de domínio projetado está ocultando? O projeto do Silverlight parece inalterado do estado criado. Se você ativar a opção para mostrar todos os arquivos para o projeto do Silverlight, você verá o culpado (consulte a Figura 4 ). Uma pasta é criada chamada Generated_Code que contém todos os o código que tenha sido projetado do projeto de servidor do parceiro. Atualmente há apenas um arquivo único lá, ExpenseReportsServer.g.cs (a significa g gerada), que contém o código para o projeto ExpenseReportsServer inteiro e está da casa da classe ExpenseContext.

fig04.gif

A Figura 4 código gerado para O projeto do Silverlight

Se você criar um novo provedor de dados ou modificar um existente, as alterações serão silenciosamente atualizadas dentro do projeto Silverlight, mantendo o cliente e o servidor sempre em sincronia. Em cenários onde os serviços são criados para o único objetivo de oferecer suporte a cliente um aplicativo, a necessidade de atualizar continuamente os proxies de serviço durante o desenvolvimento pode ser difíceis de. Esse comportamento sutil, bem como o reshaping durante a projeção, é um recurso muito útil.

Controles de dados

Portanto, tem um banco de dados, modelo de dados, serviços e proxies do lado do cliente no lugar. Agora preciso criar a interface do usuário para exibir e editar dados de relatório de despesas no cliente Silverlight. Ao lidar com aplicativos controlados por dados, normalmente existem dois tipos das maneiras de apresentar dados: tabular e formulário-com base. Ao lidar com RIAs orientado a dados, dados tabulares e baseado em formulário precisam ser apresentado de forma que faz a experiência do usuário altamente informativos e produtiva.

Silverlight 2 não possuem suporte muito forte para a apresentação de dados tabulares sair da caixa de. Nem mesmo o controle ListView (fornecia apresentação de grade básica no WPF) está presente no Silverlight, que se tornou um pouco difícil criar um software comercial. O Silverlight posteriormente recebeu um controle DataGrid, que ajudou definitivamente, mas muitos recursos necessários foram ainda não. Controles intrínsecos estavam disponíveis, como TextBox, caixa de combinação, botão, ListBox e botão de opção, que possibilitado desenvolver formulários, mas não havia suporte de alta segurança para outros comportamentos que são necessários, como validação de dados e relatórios de erros.

O Silverlight 3 apresenta um conjunto de novos controles especificamente com o objetivo de facilitar a criação de RIAs centrado em dados. Esses controles incluem DataGrid, DataForm, DataPager, FieldLabel, DescriptionViewer, ErrorSummary e ChildWindow. Eu aproveitou de DataGrid, DataForm e DataPager para habilitar a apresentação de dados em estilos de tabela e com base em formulários para meu aplicativo de exemplo. FieldLabel, DescriptionViewer e ErrorSummary fornecem a interface do usuário dinâmica, validação de dados e relatório de erro necessários para desenvolver entrada de dados respondendo. Finalmente, ChildWindow permite caixas de diálogo modal rich.

O controle DataGrid que vem com o Silverlight 3 fica robusto. Entre outras coisas, ele inclui colunas reordenáveis e redimensionáveis, agrupamento de linha, edição in-line e validação. Ele permite que você defina as colunas exibidas na grade de duas maneiras: gerado e explicitamente.

Um DataGrid que tenha sua propriedade AutoGenerateColumns definida como True criará automaticamente uma coluna para cada propriedade pública no tipo ao qual ele está vinculado — a exceção sendo que, se uma propriedade tiver um BindableAttribute anexado a ele que especifica-lo não ligável, o DataGrid não criar uma coluna para ele. Isso é um exemplo de como os novos controles de dados tirar proveito de metadados de entidade.

Quando você explicitamente define as colunas em um DataGrid, é escopo os dados exibidos para apenas as colunas que você deseja mostrar. Isso dá a você controle sobre quais tipos de coluna aos uso, bem como texto do cabeçalho e várias outras propriedades de coluna. Um DataGrid que explicitamente define suas colunas para exibir dados de relatório de despesas pode parecer a Figura 5 .

A Figura 5 DataGrid para relatório de despesas

<dataGrid:DataGrid
  x:Name="ExpenseReportDataGrid"
  AutoGenerateColumns="False">
  <dataGrid:DataGrid.Columns>
    <dataGrid:DataGridTextColumn 
      Binding="{Binding Company}" 
      Header="Company" />
    <dataGrid:DataGridTextColumn 
      Binding="{Binding Department}" 
      Header="Department" />
    <dataGrid:DataGridTextColumn 
      Binding="{Binding Description}" 
      Header="Description" />
    <dataGrid:DataGridCheckBoxColumn 
      Binding="{Binding Status}" 
      Header="Approved" />
  </dataGrid:DataGrid.Columns>
</dataGrid:DataGrid>

As propriedades que são especificadas nas ligações de coluna consulte às propriedades na entidade ExpenseReport. Lembre-se de que a classe foi projetada para o aplicativo do Silverlight, tornando utilizável no cliente. A classe DataGridTextColumn exibirá os dados como texto no modo somente leitura e como uma caixa de texto no modo de edição. O DataGridCheckBoxColumn exibirá um campo como uma caixa de seleção em todos os modos e acomodar três estados.

Para preencher um DataGrid você deve simplesmente definir sua propriedade ItemSource a qualquer objeto IEnumerable. Lembre-se de que, quando defini as despesas-serviço e o método de dados GetExpenseReports, seu tipo de retorno era IEnumerable <expensereport>. Agora apenas preciso descobrir como consumir o proxy do lado do cliente para o provedor de dados que foi gerado.

Quando a classe ExpenseService foi projetada para o aplicativo cliente, seu método GetExpenseReports foi projetado bem (após ser renomeado para LoadExpenseReports). Isso significa que deve ser capaz de criar uma instância de ExpenseService e chame seu método LoadExpenseReports apenas. Embora isso seja a abordagem correta seja, ele não produzir resultados por conta própria. Interessante, o método LoadExpenseReports não retorna nada. Como você obtém os dados de relatório de despesas?

O Silverlight requer quaisquer chamadas de bloqueio a ser executada assincronicamente, para que o thread da interface do usuário não bloquear up. Devido a isso, quando o .NET RIA Services projetos seus serviços de domínio para um cliente do Silverlight, ele refactors todas as operações de consulta para que elas não retornem nada. Isso explica por que o .NET RIA Services será renomear seus métodos de consulta a ser prefixados com carga em vez de Get (ou buscas, localizar, consulta, recuperar ou selecione), porque ela reflete o comportamento mais adequadamente.

Em vez de empregando uma abordagem de retorno de chamada, as classes geradas pelo .NET RIA Services usam um modelo de evento para notificá-lo após a conclusão de uma chamada assíncrona. Portanto, para responder ao carregamento de uma operação de consulta, você simplesmente assinar evento carregado do DomainContext, que será acionado assim que qualquer operação de consulta é concluída. Porque o manipulador de eventos é chamado no thread da interface do usuário, você pode executar qualquer ligação de dados dentro dela.

A Figura 6 carregamento de dados do relatório de despesas

public partial class Main : Page {
  ExpenseContext _dataContext;

  public Main() {
    InitializeComponent();

    this.Loaded += Main_Loaded;

    _dataContext = new ExpenseContext();
    _dataContext.Loaded += dataContext_Loaded;
  }

  void dataContext_Loaded(object sender, LoadedDataEventArgs e) {
    ExpenseReportDataGrid.ItemsSource = e.LoadedEntities;
  }

  void Main_Loaded(object sender, RoutedEventArgs e)  {
    _dataContext.LoadExpenseReports();
  }
}

fig07.gif

A Figura 7 grade de relatórios de despesas da interface do usuário

Depois que o manipulador de eventos carregado foi acionado, há duas maneiras de recuperar os dados solicitados: através a propriedade LoadedDataEventArgs.LoadedEntities ou através de uma das propriedades com rigidez de tipos de entidade em seu contexto de domínio (como ExpenseContext). Acesso os dados através de argumentos de evento é a abordagem preferida pois isso garante que você obter apenas os dados que você deseja. Sempre que uma operação de consulta for chamada, a entidade retornada instâncias serão adicionadas ao contexto de domínio, portanto, sempre que você acessar seu conteúdo, você irá obter o acúmulo de cada consulta, não apenas que você executou recentemente.

Implementar a lógica para recuperar todos os relatórios de despesas e preenchendo o DataGrid com os dados retornados pode parecer como Figura 6 . Neste ponto, nossa RIA controlados por dados parece Figura 7 .

Bem como ser capaz de carregar dados, a classe DomainContext (e seus subtipos gerados) contém métodos para gerenciar o controle de alterações e persistência dos dados. Cada alteração feita a uma entidade que foi recuperada (ou adicionada ou excluída) por meio de um DomainContext será rastreada. Quando você deseja salvar as alterações, você pode simplesmente chamar o método de SubmitChanges:

private void SaveChangesButton_Click(
  object sender, RoutedEventArgs e) {
  if (_dataContext.HasChanges) {
    _dataContext.SubmitChanges();
  }
}

Desde que o DataGrid permite embutido edição por padrão, você pode modificar qualquer um dos registros do relatório de despesas e clique no botão Salvar alterações para mantê-los para o servidor. Quando o método SubmitChanges é chamado, o DomainContext monta uma alteração definida para todas suas entidades controladas e a envia para o DomainService correspondente. Em seguida, o DomainService decompõe o conjunto de alteração e chama a operação do respectivo domínio para cada entidade que foi inserida, atualizada ou excluída.

Quando você estiver editando dados dentro do controle DataGrid, ele fornece validação e automaticamente relatórios de erros. Se você inserir dados inválidos, você será visualmente notificado com uma explicação sobre o que há de errado (consulte a Figura 8 ). Esse recurso vem sem qualquer configuração ou trabalho de sua parte. Como você verá posteriormente no artigo, você também pode definir regras de validação personalizadas em seu modelo de dados que será retirado e garantido, o DataGrid.

fig08.gif

A Figura 8 DataGrid validação

Este modo de exibição dos relatórios de despesas é bom, mas seria muito mais útil ver as despesas agrupadas por status. Dessa forma que imediatamente pode descobrir que os relatórios são aguardando aprovação por um gerente. Felizmente, isso pode ser conseguido facilmente no Silverlight 3 anexando este código para a definição de DataGrid existente:

<dataGrid:DataGrid.GroupDescriptions>
  <dataGrid:PropertyGroupDescription  
    PropertyName="Status" />
</dataGrid:DataGrid.GroupDescriptions>

O resultado é semelhante A Figura 9 .

fig09.gif

A Figura 9 DataGrid agrupamento

ObjectDataSource

Enquanto alguns desenvolvedores preferirá os dados imperativos ligação abordagem usada até o momento, outros podem como poder executar ligação puramente declarativamente, bem como da forma que eles podem trabalha com o ObjectDataProvider no WPF de dados. Para isso, o Silverlight 3 apresenta o controle ObjectDataSource.

ObjectDataSource é um controle nonvisual que sabe como trabalhar especificamente com tipos de DomainContext. Ele pode ser pensado como fornecer toda a funcionalidade que a abordagem imperativa anterior foi mas por meio totalmente declarativo (mais mais). Você simplesmente dizer-que tipo de DomainContext você está trabalhando e qual das suas operações de consulta você deseja chamado. ObjectDataSource tratará o restante.

Você poderia remover todo o código da seção anterior e substituí-lo por um ObjectDataSource, e ele automaticamente chamará o método de carregamento especificado:

<ria:ObjectDataSource
  x:Name="ExpenseReportsObjectDataSource"
  DataContextType="ExpenseReports.ExpenseContext"
  LoadMethodName="LoadExpenseReports"
  PageSize="20">
  <ria:ObjectDataSource.Filter>
    <data:FilterDescriptor Member="Department" 
      Operator="IsEqualTo" Value="IT" />
  </ria:ObjectDataSource.Filter>
  <ria:ObjectDataSource.Sort>
    <data:SortDescriptor Member="Status" 
      Direction="Descending" />
  </ria:ObjectDataSource.Sort>
</ria:ObjectDataSource>

Usando o controle ObjectDataSource apenas não fornecer uma forma declarativa de ligação de dados, ele também torna as consultas compostas. Você pode adicionar classificação, grupo e expressões de filtro um ObjectDataSource, bem como tamanho da página, que será aplicado para a chamada para sua operação de consulta. A melhor parte é que o ObjectDataSource modificará as solicitações feitas para o serviço de domínio para que qualquer especificado classificação ou filtragem seja aplicada no lado do servidor, que significa que nenhum dado desnecessários serão passados durante a transmissão.

Lembre-se a propriedade de IsComposable do DomainOperationAttribute? Esse é o que determina se uma operação de domínio permite parâmetros de consulta adicionais a serem passados a ele, fazer os dados retornados compostas. Meu método GetExpenseReports não adicionar qualquer código de classificação ou filtragem, mas devido a composição do DomainService e o fato de que o ObjectDataSource sabe como tornar composto consultas, POSSO obter essa funcionalidade automaticamente.

ObjectDataSource é realmente um invólucro para uma instância de DomainContext e, portanto, se beneficia do mesmo comportamento do controle de alterações. Ele contém os mesmos métodos Load e SubmitChanges que DataContext faz, permitindo que você programaticamente controlá-lo como faria um DomainContext.

DataPager

Como a quantidade de dados atualmente sendo exibidos na grade é um pouco complicada, ele provavelmente faria sentido para ele para fins de apresentação. Enquanto o controle DataGrid propriamente dito não incluir o comportamento de paginação, o Silverlight 3 introduz um novo controle de DataPager funciona perfeitamente com outros controles de dados para facilmente fornecer funcionalidade de paginação juntamente com eles.

O controle DataPager simplesmente apresenta a INTERFACE necessária de paginação através de uma fonte de dados fornecido. Se você acoplar um DataPager para a mesma fonte de dados de outro controle dados (como DataGrid), paginação através os dados usando o DataPager também será paginar os dados exibidos em outros controles acoplados. O código para adicionar um DataPager à lista de relatórios de despesas pode ser assim:

<data:DataPager
  Source="{Binding Mode=TwoWay, Source={StaticResource ExpenseReportDataSource}, Path=Data}"/>

fig10.gif

A Figura 10 DataPager feitos na parte inferior de um DataGrid

Observe que tudo o que precisa fazer é definir o controle e vinculá-lo para a fonte correta e o controle manipulará o restante (consulte a A Figura 10 ).

O DataPager tem vários modos você pode selecionar que variam de acordo como ele apresenta as páginas disponíveis para o usuário. Além disso, você pode re-skin completamente o DataPager para procurar no entanto você gostaria, mantendo ainda a sua funcionalidade existente.

Enquanto o DataGrid e DataPager fornecem um embutido grande experiência de edição, e se você quisesse exibir dados em um layout com base em formulários? Para criar ou modificar relatórios de despesas, você deseja fornecer ao usuário um formulário intuitivo para ser usado em vez de contar com a grade. Para que você pode utilizar o novo controle DataForm.

DataForm

O controle DataForm permite que você defina um conjunto de campos que será exibido em um layout com base em formulários e podem ser vinculados a uma instância de entidade única ou uma coleção. Ele permite trabalhar com os dados em somente leitura, inserir e editar modos, com a capacidade de personalizar a aparência de cada. Opcionalmente, você pode mostrar controles para alternar entre modos e, quando vinculado a uma coleção, o DataForm também pode mostrar um pager para navegação. Assim como o DataGrid, os DataForm também vem em vários formulários: gerado, explícita e o modelo.

O modo gerado funciona exatamente como DataGrid. Ele criará um par de campo e o rótulo para cada propriedade pública no tipo que ele está vinculado ao. DataForm respeita a BindableAttribute também, que lhe permite definir a lista de campos ligáveis no nível de entidade. Definir um formulário de edição para relatórios de despesas pode ser tão simples como este:

<dataControls:DataForm
  x:Name="ExpenseReportDataForm" Header="Expense Report"
  ItemsSource="{Binding Source={StaticResource ExpenseReportsObjectDataSource}, Path=Data}" />

Usando o modo gerado, você está permitindo que o DataForm fazer todas as suposições de interface do usuário com base em metadados da entidade. O formulário resultante é mostrado na Figura 11 .

fig11.gif

A Figura 11 formulário de entrada despesas derivados DataForm

Rótulos para qualquer campos necessários (as propriedades marcadas com obrigatório-atributo) são exibidos em negrito, indicando a necessidade do usuário. Além disso, para a direita dos controles de entrada, a marca de informações fornece uma descrição de dica de ferramenta de controle do mouse da entrada esperada. Uma descrição é opcional e é recuperada por examinar se a propriedade do campo correspondente possui um DescriptionAttribute anexado a ele. Estes são dois mais exemplos de como os novos controles dados 3 Silverlight habilitar cenários de orientado a dados, adicionando a sua interface do usuário em resposta a metadados em seus modelos de dados.

Assim como o DataGrid, o DataForm também fornece validação de dados e relatórios de erros. Os dois controles ter uma aparência consistente e funcionalidade, que fornece uma experiência de usuário geral boa independentemente do que apresentação de dados que você precisa.

Usando o formulário explícito, você pode declarar que campos que você deseja exibem, o tipo de campos a serem usados e o texto de rótulo para exibir (entre outros itens). Este formulário é útil quando você não deseja deixar a criação de interface do usuário para os metadados DataForm e entidade. Declarar um DataForm explicitamente pode parecer como Figura 12 .

A Figura 12 criação explícita de um DataForm

<dataControls:DataForm
  x:Name="ExpenseReportDataForm"
  ItemsSource="{Binding Source={StaticResource ExpenseReportsObjectDataSource}, Path=Data}"
  AutoGenerateFields="False">
  <dataControls:DataForm.Fields>
    <dataControls:DataFormTextField 
      Binding="{Binding Company}" Label="Company" />
    <dataControls:DataFormTextField 
      Binding="{Binding Department}" Label="Department" />                
    <dataControls:DataFormTextField 
      Binding="{Binding Description}" Label="Description" />
    <dataControls:DataFormCheckBoxField 
      Binding="{Binding Status}" Label="Approved" />
  </dataControls:DataForm.Fields>
</dataControls:DataForm>

Em adição para campos texto e caixas de seleção, existem campos disponíveis para data, caixa de combinação, modelo, separador, cabeçalho e grupo de campos. Com elas, você pode definir explicitamente os campos desejados para mostrar e fornecer instruções básicas ao DataForm para exibi-los. Mesmo com essa flexibilidade, você está ainda restrito para um início forma, onde cada campo é um tradicional suspensa Rótulo e entrada controlam par. Enquanto o DataFormTemplateField permite que você definir um modelo para todos os modos (exibir e editar), ele está limitado para o nível de campo. E se você quisesse ao modelo o formulário inteiro?

Quando controle total sobre sua interface do usuário é necessária (ou desejado), o DataForm permite que você definir modelos de dados personalizados para cada um dos seus modos (exibir, inserir e editar). Com essa capacidade, você pode interromper o estilo de formulário de cima para baixo de padrão e criar qualquer aparência faz sentido para sua situação.

Determinado comportamento é global para todas as três formas do DataForm, como navegação, validação e relatório de erros. Quando você escolhe redefinir os modelos de dados no entanto, você perde o campo automática rótulo e descrição visualizador, que trabalhou com metadados do modelo. Ao desenvolver um aplicativo orientado a dados, seria uma pena perder esse comportamento útil apenas porque é necessária personalizar o layout. Felizmente, os controles usados internamente pelo dataform e que fornecem esse comportamento também são utilizáveis manualmente.

Os metadados

Quando você permitir que o DataForm gerar a lista de campos para você, ele automaticamente faz usar de dois controles para fornecer o comportamento de rótulo e descrição: FieldLabel e DescriptionViewer. Ambos esses controles são fáceis de usar e podem ser utilizados em qualquer cenário ligados a dados, incluindo modelos DataForm personalizados.

FieldLabel é útil quando você deseja exibir um rótulo para um controle que é determinado dos metadados de sua propriedade limite associado. O texto usado para a etiqueta é derivado da propriedade Name do DisplayAttribute associada a propriedade que está vinculado a. Além disso, se a propriedade for necessária (representado por ter um RequiredAttribute marcado como true anexado a ele), o texto de rótulo do campo será ser em negrito.

Além de poder especificar um nome personalizado com o DisplayAttribute, você pode especificar uma descrição de uma propriedade. Se você quiser exibir descrição um campo, você pode usar o controle DescriptionViewer, que trata isso para você automaticamente. Ela será exibida uma marca de informações que fornece uma dica de ferramenta contendo a descrição da propriedade que está associada.

Com os controles FieldLabel e DescriptionViewer, você pode desenvolver formulários de dados personalizado que aproveitam os metadados do seu modelo de dados sem precisar replicar informações (como nomes de campo e descrições). Se você usar esses controles em seu aplicativo, qualquer que uma alteração for feita com uma propriedade nome, descrição ou status necessária (no nível do modelo), sua interface do usuário automaticamente refletirá a alteração devido a sua dependência no modelo de dados. Esse é o tipo de comportamento esperado ao desenvolver aplicativos controlados por dados.

Validação

Como nós estiver enfocando o desenvolvimento de aplicativos orientados a dados, gostaríamos de manter nosso lógica comercial e a validação próximo ao modelo de dados. Ao usar o .NET RIA Services você pode expressar lógica de validação de duas maneiras: anotações de dados e lógica personalizada/compartilhado.

A versão do SP1 Microsoft .NET Framework 3.5 introduziu um conjunto de atributos chamado anotações de dados que são destinadas a associar metadados e regras de validação a um modelo de dados. Essas anotações inicialmente foram usadas pelos dados dinâmica do ASP.NET e são compreendidas e respeitadas .NET RIA Services e o novo 3 Silverlight controles de dados. Com elas você pode expressar esses aspectos de validação como tamanho da seqüência de caracteres, intervalo, tipo de dados e restrições de expressão regular:

[Bindable(true, BindingDirection.TwoWay)]
[Display(Name = "Expense Amount", 
  Description = "The amount of the incurred expense.")]
[Range(0.0, 1000000.00)]
public object Amount;

[Bindable(true, BindingDirection.TwoWay)]
[Display(Name = "Category", 
  Description = "The category of expense, i.e., mileage.")]
[StringLength(10)]
public object Category;

Quando a projeção de serviços de RIA .NET processo é executado, ele faz um ponto para fluxo de quaisquer anotações de dados do lado do servidor para o cliente. Contanto que você aproveitar as anotações de dados genéricos para representar as regras de validação em seu modelo de dados do lado do servidor, que a validação irá executar sobre a seu aplicativo do Silverlight e ser totalmente usado pelos controles cientes (DataForm, DataGrid, FieldLabel e assim por diante). Isso efetivamente lhe validação do cliente e servidor.

Usar as anotações de dados é fácil, mas eles não é possível expressar cada requisito de validação possíveis. Na verdade, elas realmente representam apenas os cenários mais comuns. Para outras situações você provavelmente precisará definir a validação imperativa. Embora isso seja simples para fazer, o processo de projeção dos serviços de RIA de .NET não pode fluir apenas sua lógica personalizada para o cliente como esse processo é limitado a criação de suas classes de proxy DataContext e entidade.

Você pode manter a lógica personalizada no servidor e fazer um serviço de chamada para ele de seu cliente do Silverlight, mas que pode eliminar a capacidade de resposta do aplicativo e remover a capacidade dos controles de dados determinar a validade de dados automaticamente. Você pode copiar e colar a lógica para o cliente e executar validação manualmente em ambas as camadas, mas duplicação de código nunca é uma coisa boa, e o problema de validação automática ainda deve existir. Este é um cenário que merece o uso do recurso de código compartilhado .NET RIA Services.

Código compartilhado

Para o aplicativo de relatório de despesas, preciso Certifique-se duas regras de validação personalizada: qualquer relatório de exportação sobre 1.000,00 deve incluir uma descrição de estrutura de tópicos sua finalidade e não despesas relatórios podem ser arquivados para compras futuras. Nenhuma dessas condições pode ser atendida usando anotações de dados, mas pode expressá-los facilmente imperativa.

.NET RIA Services inclui um recurso chamado código compartilhado que permite que você possa definir lógica no seu projeto de servidor que serão sincronizados e está disponível em seu aplicativo de cliente. Durante o processo de projeção de código, qualquer código marcado como compartilhada será copiado entre os projetos em vez de convertida e intermediadas por proxy. Para aproveitar este recurso, você primeiro criar um novo arquivo de código em seu projeto de servidor com o sufixo do .Shared. [idioma extensão] (por exemplo, ExpenseData.shared.cs). Quando o processo de projeção de código é executado, ele será especificamente procurar por arquivos dentro do projeto com esse sufixo e tratá-la código como compartilhado.

Há uma nova anotação de dados no 4.0 do Framework .NET chamado CustomValidationAttribute que permite que você associar uma regra de validação personalizada um modelo de dados na entidade ou o nível de propriedade. Especificar minhas duas regras de validação personalizada pode parecer com isso:

[MetadataType(typeof(ExpenseReportDetailsMetadata))]
[CustomValidation(typeof(ExpenseReportValidation),
  "ValidateDescription")]
public partial class ExpenseReportDetails { }

public partial class ExpenseReportDetailsMetadata {
  [Bindable(true, BindingDirection.TwoWay)]
  [CustomValidation(typeof(ExpenseReportValidation), 
    "ValidateDateIncurred")]
  [Display(Name = "Date", 
    Description = "The date of when this expense was incurred.")]
  public object DateIncurred;

O processo de projeção de serviços de RIA .NET é atento a CustomValidationAttribute e fluirá para o proxy de cliente. Como a validação personalizada está contida em um tipo de validação (que você provavelmente deseja definir no servidor), você pode aproveitar código compartilhado para manipular sua sincronização.

A assinatura de um método de validação personalizada deve seguir um padrão específico:

[Shared]
public static class ExpenseReportValidation {
  public static bool ValidateDateIncurred(object property, 
    ValidationContext context, out ValidationResult validationResult) {

    validationResult = null;
    bool result =       DateTime.Compare((DateTime)property, DateTime.Now) < 0;

    if (!result)
      validationResult = new ValidationResult(context.DisplayName + 
        " must be today or in the past.");
      return result;
  }
}

Observe o uso do SharedAttribute na classe ExpenseReportValidation. Isso significa para o processo de projeção que ele não precisa ser convertido em cliente porque ele também será abordado, sendo uma parte do código compartilhado.

Dispor para cima

Antigamente, você deve desenvolver aplicativos de relatório de despesas envolvendo as operações CRUD em torno os dados de relatório de despesas. O Silverlight 3 DataGrid, DataForm, DataPager e ObjectDataSource novo permitem que você criar a interface do usuário rapidamente sem precisar investir no desenvolvimento de infra-estrutura ou sacrificar a funcionalidade para empregar os controles internos. Além disso, usando o .NET RIA Services você pode definir lógica comercial do lado do servidor completa com as regras de validação e o acesso a dados e fazer com que ele ser facilmente consumíveis graças ao processo de projeção.

Meu relatório de despesas de exemplo ainda precisa de uma seção de detalhes do relatório, bem como navegação e autenticação. Para fazer isso, será preciso usar alguns controles adicionais introduzidos no 3 do Silverlight, bem como um conjunto de serviços de aplicativos fornecidos pelo .NET RIA Services. Em um artigo futuro, demonstrarei como isso funciona.

Carter Jonathané um divulgador técnico da Microsoft.