De qualquer forma, em 2011 pretendo dar um pouco mais de atenção ao SilverLight e um bom começo é ter o ambiente corretamente ajustado para poder usar a tecnologia.
Lembrando que o SilverLight foi lançado em meados de 2007 e desde
então tem evoluído muito. Enquanto o ASP .NET é uma plataforma para
desenvolvimento do lado do servidor, com a chegada do
SilverLight o foco muda para o lado do cliente, visto que uma aplicação
SilverLight roda no navegador do cliente em uma versão específica da
CLR (Common Language Runtime).
Se o SilverLight é algo totalmente novo mas você conhece o WPF, fique sabendo que o SilverLight pode ser considerado como uma versão reduzida do WPF, sendo possível inclusive reutilizar código escrito no SilverLight 4 no WPF 4 e vice-versa.
Preparando o terreno
Para iniciar o desenvolvimento de aplicações SilverLight precisamos ter instalados as ferramentas e os SDKs necessários.
- Instalar o Visual Studio 2010 ou o Visual Web Developer Express 2010;
- Instalar o SilverLight 4 Tools for Visual Studio 2010;
- Instalar o Expression Blend 4;
Após baixar e instalar as ferramentas acima, não ocorrendo nenhum erro, tudo estará pronto para você dar início a saga de desenvolver aplicações para o SilverLight.
Invocando um serviço que expõe dados
Ao trabalhar em um projeto Silverlight que envolve serviços, o WCF (Windows Comunication Foundation) é a escolha preferida para a construção de um serviço se temos que criar este e o aplicativo cliente do Silverlight que irá utilizá-lo. Dessa forma, usando o WCF temos um controle completo sobre quais tipos serão enviados para o cliente.
Quando queremos construir uma aplicação Silverlight que trabalha com os dados disponíveis sobre o serviço (talvez proveniente de um banco de dados ou outro serviço externo), precisamos ter em mente as seguintes questões:
- Como devemos projetar o serviço que expõe os dados de modo que possa ser acessível a partir do Silverlight?
- Como devemos proceder para projetar o aplicativo do Silverlight, para que ele se comunica com o serviço?
Para mostrar como podemos nos conectar a partir do Silverlight com um serviço WCF que expõe os dados, vamos começar com a concepção do próprio serviço.
Observação: O artigo foi baseado em alguns exemplos prontos que foram adaptados e ajustados.
Vamos supor que estamos construindo uma aplicação que contém uma janela de visualização onde o usuário poderá obter informações sobre os funcionários de uma empresa.
Os dados do funcionário serão expostos pelo serviço e o aplicativo Silverlight irá se conectar ao serviço e trabalhar com os dados no lado do cliente. A seguir estão os passos que precisa executar para começar este trabalho.
Dica importante: para tornar mais simples o exemplo não vamos trabalhar com um banco de dados mas iremos preencher os dados via código.
Dando a partida: criando a aplicação SilverLight 4
Abra o Visual Web Developer 2010 Express Edition no menu File selecione New Project. A seguir selecione Visual C# e o template SilverLight > SilverLight Application, informe o nome SilverLightFuncionarios e clique em OK.
Será apresentada a janela abaixo, onde vemos o projeto do tipo ASP .NET Web Application Project selecionado como o tipo de projeto web que será usado para hospedar a aplicação SilverLight. Note que a versão do SilverLight escolhida é a versão 4.
Após fazer estas verificações clique em OK. Será criada uma solução com dois projetos:
- O projeto SilverLight: SilverLightFuncionarios;
- O projeto Web: SilverLightFuncionarios.web - responsável por hospedar o conteúdo SilverLight;
Vamos nos concentrar primeiro na aplicação web. Os dados que desejamos expor consistem em informações sobre os funcionários. Vamos então definir as classes que representam o domínio do nosso problema.
A seguir vemos o diagrama de classes que mostra as classes do nosso domínio:
O diagrama de classes acima mostra o relacionamento entre as classes e
a enumeração CarroTipo. Note que a classe Funcionario é uma classe
abstrata e que as demais classes herdam da classe Funcionario.
Dica Importante: Utilizando classes abstratas você pode declarar classes que definam apenas parte de uma implementação, deixando que as classes estendidas forneçam a implementação específica de alguns ou de todos os métodos. Uma classe abstrata em geral será destinada apenas a servir como base para a criação de outras classes.
O próximo passo é criar as classes no projeto SilverLightFuncionarios.web. Vamos começar criando a classe Funcionario. Selecione o projeto SilverLightFuncionarios.web e no menu Project clique em Add Class. A seguir informe o nome Funcionario.cs e clique no botão Add.
Repita o procedimento acima e crie as seguintes classes:
- Gerente.cs
- Consultor.cs
- Administrativo.cs
- CarroTipo.cs
Após criar os arquivos para as classes vamos definir o código da classe Funcionario.cs conforme abaixo:
using System;
using System.Runtime.Serialization;
namespace SilverlightFuncionarios.Web
{
[DataContract]
[KnownType(typeof(Gerente))]
[KnownType(typeof(Consultor))]
[KnownType(typeof(Administrativo))]
public abstract class Funcionario
{
[DataMember]
public int funcionarioid { get; set; }
[DataMember]
public string nome { get; set; }
[DataMember]
public string email { get; set; }
[DataMember]
public DateTime nascimento { get; set; }
}
}
Vamos entender a classe Funcionario:
- A classe é uma classe abstrata e isso indica que a classe não pode ser instanciada e deve ser herdada por outra classe.
- O atributo DataContract usado na classe indica que o tipo pode ser enviado via rede.
- DataContract especifica que o tipo define ou implementa um contrato de dados e é serializável por um serializador, como o DataContractSerializer. Para fazer o seu tipo serializável, os autores do tipo devem definir um contrato de dados para o seu tipo.
- O atributo DataMember usado nos campos da classe será incluído no tipo e poderá ser enviado via rede para o lado do cliente.
- O DataMember quando aplicado ao membro de um tipo, especifica que o membro faz parte de um contrato de dados e é serializável pelo DataContractSerializer.
- O atributo KnowType marca os tipos a serem incluídos no processo de serialização.
- O atributo de classe KnownType permite que você especifique, antecipadamente, os tipos que devem ser incluídos para apreciação durante a desserialização.
Agora vamos definir o código da classe Gerente conforme o código a seguir:
using System.Runtime.Serialization;
namespace SilverlightFuncionarios.Web
{
public class Gerente : Funcionario
{
[DataMember]
public double Gratificacao { get; set; }
[DataMember]
public CarroTipo Carro { get; set; }
}
}
A classe Gerente herda da classe Funcionario e define os campos:
- Gratificacao
- Carro
Observação: Para poder usar os atributos DataContrat e DataMember, talvez você terá que incluir uma referência a System.RunTime.Serialization no projeto web.
Abaixo temos a definição da classe Consultor:
using System.Runtime.Serialization;
namespace SilverlightFuncionarios.Web
{
public class Consultor : Funcionario
{
[DataMember]
public CarroTipo Carro { get; set; }
}
}
A classe Consultor herda da classe Funcionario e define o campo:
- Carro
Observação: Para poder usar os atributos
DataContrat e DataMember talvez você terá que incluir uma referência a
System.RunTime.Serialization no projeto web.
A seguir temos o código da classe Administrativo:
namespace SilverlightFuncionarios.Web
{
public class Administrativo : Funcionario
{
}
}
A classe Administrativo herda da classe Funcionario. Finalmente o código da enumeração CarroTipo:
namespace SilverlightFuncionarios.Web
{
public enum CarroTipo
{
Esportivo,
Passeio,
Utilitario
}
}
Nosso próximo objetivo será criar uma classe chamada FuncionarioRepositorio que será usada para carregar os dados de exemplo. Em uma aplicação real geralmente carregaríamos os dados de um banco de dados. Para criar a classe repita o procedimento usado para criar a classe Funcionario.
O código desta classe é definido como:
using System;
using System.Collections.Generic;
namespace SilverlightFuncionarios.Web
{
public class FuncionarioRepositorio
{
private static List<Funcionario> todosFuncionarios;
public FuncionarioRepositorio()
{
carregaFuncionarios();
}
private void carregaFuncionarios()
{
if (todosFuncionarios == null)
{
todosFuncionarios = new List<Funcionario>()
{
new Gerente()
{
funcionarioid=1,
nome="Macoratti",
email="macoratti@yahoo.com",
carro=CarroTipo.Esportivo,
gratificacao=10000.00,
nascimento=new DateTime(1960, 1, 1)
},
new Consultor()
{
funcionarioid=2,
nome="Mario",
email="mario@bol.com.br",
carro=CarroTipo.Passeio,
nascimento=new DateTime(1976, 11, 9)
},
new Consultor()
{
funcionarioid=3,
nome="Janice",
email="janice@uol.com.br",
carro=CarroTipo.Utilitario,
nascimento=new DateTime(1983, 3, 12)
},
new Consultor()
{
funcionarioid=4,
nome="Jefferson",
email="jeff@net.com",
carro=CarroTipo.Passeio,
nascimento=new DateTime(1984, 6, 7)
},
new Administrativo()
{
funcionarioid=5,
nome="Miriam",
email="miriam@uol.com.br",
nascimento=new DateTime(1970, 9, 22)
},
new Administrativo()
{
funcionarioid=6,
nome="Jose",
email="jose@bol.com.br",
nascimento=new DateTime(1973, 2, 19)
},
};
}
public List<Funcionario> TodosFuncionarios
{
get
{
return todosFuncionarios;
}
}
}
}
A classe FuncionarioRepositorio usa uma lista estática de funcionários: private static List<Funcionario> todosFuncionarios.
Agora que definimos as classes do nosso domínio e a classe que irá popular os objetos da nossa classe vamos incluir um serviço WCF no projeto web. Selecione o projeto SilverLightFuncionarios.web e no menu Project clique em Add New Item.
Na caixa de diálogo Add New Item veremos duas opções para adicionar um serviço WCF:
- SilverLight-enabled WCF Service;
- WCF Service;
Vamos selecionar o item SilverLight-enabled WCF Service e informe o nome FuncionarioService e clique no botão Add. Será criado um arquivo FuncionarioService.csv conforme a figura abaixo:
Vamos fazer alguns ajustes no arquivo FuncionarioService gerado para habilitar os serviços WCF. O serviço deverá apresentar dois métodos:
- O primeiro deverá recuperar todos os funcionários;
- O outro deverá recuperar um funcionário pela identificação do funcionário;
Cada método deverá estar disponível no lado do cliente e deverá ter o atributo OperacionContract que indica que o método define uma operação que é parte de um contrato de serviço em uma aplicação.
Dessa forma o código implementado no serviço deverá chamar o método TodosFuncionarios da classe FuncionarioRepositorio conforme abaixo:
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.Collections.Generic;
namespace SilverlightFuncionarios.Web
{
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class FuncionarioService
{
[OperationContract]
public List<Funcionario> GetTodosFuncionarios()
{
return new FuncionarioRepositorio().TodosFuncionarios;
}
[OperationContract]
public Funcionario GetFuncionarioPorId(int funciid)
{
return new FuncionarioRepositorio().TodosFuncionarios.Where
(e => e.funcionarioid == funciid).FirstOrDefault();
}
}
}
Com isso encerramos os ajustes do lado do cliente e podemos partir para o lado do servidor. Antes de continuar compile a aplicação (Build). Selecione o projeto SilverLight SilverLightFuncionarios e no menu Project clique em Add Service Reference.
A seguir clique no botão Discover e selecione: Services in Solution. Se o serviço estiver disponível ele deverá aparecer na caixa Services.
Se o serviço puder ser conectado sem erros, ele irá aparecer na lista de serviços e em Operations serão exibidos os nomes dos métodos criados. A seguir selecione o serviço e informe o nome FuncionarioService e clique no botão OK.
Depois de clicar no botão OK, Visual Web Developer 2010 Express tentará construir um proxy que é mais ou menos uma cópia do lado do cliente da classe de serviço. Na janela Solution Explorer o serviço poderá ser visto conforme mostra a figura a seguir:
Vamos agora cuidar da interface onde iremos usar o serviço criado. Selecione o projeto SilverLightFuncionarios e no menu Project clique em Add Reference e inclua referência a System.Windows.Controls.Data. Em seguida clique com o botão direito do mouse sobre o projeto e selecione Add > New Folder e informe o nome Converters para a pasta criada.
Depois vamos incluir dois arquivos que serão usados como conversores de data para o campo Nascimento e moeda para o campo Gratificação:
- CurrencyConverter.cs
- ShortDateConverter.cs
Observação: Não vou entrar em detalhes sobre os conversores, irei apenas usá-los no projeto. Se você quiser saber mais sobre eles consulte este link.
Selecione o arquivo MainPage.xaml e a partir da ToolBox inclua os seguintes controles conforme o leiaute da figura abaixo;
- TextBlock
- TextBox
- Button
- DataGrid
A seguir temos a exibição do código XAML correspondente:
<UserControl xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
x:Class="SilverlightFuncionarios.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:local="clr-namespace:SilverlightFuncionarios.Converters" Loaded="UserControl_Loaded"
d:DesignHeight="450" d:DesignWidth="550" DataContext="{Binding}">
<UserControl.Resources>
<local:ShortDateConverter x:Key="localShortDateConverter"></local:ShortDateConverter>
<local:CurrencyConverter x:Key="localCurrencyConverter"></local:CurrencyConverter>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" Height="450" Width="550">
<Grid.RowDefinitions>
<RowDefinition Height="60"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock x:Name="TitleTextBlock" Grid.Row="0" Grid.ColumnSpan="2" Text="Macoratti.net - SilverLight" FontSize="24" FontWeight="Bold" HorizontalAlignment="Center"></TextBlock>
<StackPanel Grid.Row="1" Background="Bisque" >
<Grid Margin="3" HorizontalAlignment="Center" >
<Grid.RowDefinitions>
<RowDefinition Height="50"></RowDefinition>
<RowDefinition Height="300"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"></ColumnDefinition>
<ColumnDefinition Width="2*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Grid.ColumnSpan="2">
<TextBlock x:Name="FunciIDTextBlock" VerticalAlignment="Center" Margin="2">Cod. Funci.::</TextBlock>
<TextBox x:Name="FunciIDTextBox" HorizontalAlignment="Left" Width="200" VerticalAlignment="Center" Margin="2"></TextBox>
<Button x:Name="ProcuraFunciButton" Click="ProcuraFunciButton_Click" Margin="2" Content="Procurar Funci" Width="120" Height="30" HorizontalAlignment="Center"></Button>
<TextBlock x:Name="ErrorTextBlock" Foreground="Red"></TextBlock>
</StackPanel>
<data:DataGrid x:Name="FunciDataGrid" Grid.Row="1" SelectionChanged="FunciDataGrid_SelectionChanged" CanUserReorderColumns="False"
IsReadOnly="True" AutoGenerateColumns="False" Width="316" Margin="0,0,3,0">
<data:DataGrid.Columns>
<data:DataGridTextColumn Binding="{Binding funcionarioid}" Header="Cod.Funci"></data:DataGridTextColumn>
<data:DataGridTextColumn Binding="{Binding nome}" Header="Nome"></data:DataGridTextColumn>
<data:DataGridTextColumn Binding="{Binding email}" Header="Email"></data:DataGridTextColumn>
</data:DataGrid.Columns>
</data:DataGrid>
<Grid x:Name="DetailGrid" Grid.Row="1" Grid.Column="1" Width="219" Margin="3,3,-10,3">
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="100"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock x:Name="FunciIdTextBlock" Grid.Row="0" Grid.Column="0" Text="Cod. Funci:" FontWeight="Bold"></TextBlock>
<TextBlock x:Name="FunciIdValueTextBlock" Grid.Row="0" Grid.Column="1" Text="{Binding funcionarioid}"></TextBlock>
<TextBlock x:Name="NomeTextBlock" Grid.Row="1" Grid.Column="0" Text="Nome:" FontWeight="Bold"></TextBlock>
<TextBlock x:Name="NomeTextBlockValue" Grid.Row="1" Grid.Column="1" Text="{Binding nome}" ></TextBlock>
<TextBlock x:Name="EmailTextBlock" Grid.Row="2" Grid.Column="0" Text="Email:" FontWeight="Bold"></TextBlock>
<TextBlock x:Name="EmailTextBlockValue" Grid.Row="2" Grid.Column="1" Text="{Binding email}" ></TextBlock>
<TextBlock x:Name="NascimentoTextBlock" Grid.Row="3" Grid.Column="0" Text="Nascimento:" FontWeight="Bold"></TextBlock>
<TextBlock x:Name="NascimentoTextBlockValue" Grid.Row="3" Grid.Column="1" Text="{Binding nascimento, Converter={StaticResource localShortDateConverter}}" ></TextBlock>
<TextBlock x:Name="GratificacaoTextBlock" Grid.Row="4" Grid.Column="0" Text="Gratificação:" FontWeight="Bold"></TextBlock>
<TextBlock x:Name="GratificacaoTextBlockValue" Grid.Row="4" Grid.Column="1" Text="{Binding gratificacao, Converter={StaticResource localCurrencyConverter}}" ></TextBlock>
<TextBlock x:Name="CarroTextBlock" Grid.Row="5" Grid.Column="0" Text="Carro:" FontWeight="Bold"></TextBlock>
<TextBlock x:Name="CarroTextBlockValue" Grid.Row="5" Grid.Column="1" Text="{Binding carro}" ></TextBlock>
</Grid>
</Grid>
</StackPanel>
</Grid>
</UserControl>
Na interface teremos as informações dos funcionários exibidas no controle DataGrid e os seus detalhes nos controles TextBlock conforme o funcionário selecionado. Observe o uso dos conversores localShortDateConverter e localCurencyConverter para os campos nascimento e gratificação.
Observe que estamos utilizando os recursos do DataBinding, vinculando cada controle ao nome do membro e definido na classe de domínio. A classe Binding está no namespace System.Windows.Data e é responsável por manter a comunicação entre a origem e o destino, expondo uma série de propriedades que nos permite customizar o comportamento dessa comunicação.
Entre as principais propriedades, temos:
- ElementName: define o nome do elemento que servirá como fonte. Utilize esta propriedade quando desejar preencher uma outra propriedade com o valor de um controle do WPF.
- Mode: determina a direção das informações.
- NotifyOnSourceUpdated: valor boleano indicando se o evento SourceUpdated é disparado quando alguma atualização na fonte das informações ocorrer.
- NotifyOnTargetUpdated: valor boleano indicando se o evento SourceUpdated é disparado quando alguma atualização no destino das informações ocorrer.
- Path: espefica o nome da propriedade que será exibida.
- RelativeSource: especifica uma fonte de forma relativa à posição do objeto atual.
- Source: define o nome do objeto que servirá como fonte. Utilize esta propriedade quando desejar preencher com uma instância de um objeto.
- XPath: a mesma finalidade da propriedade Path, mas define uma expressão XPath quando a fonte de informações for um arquivo Xml.
Dessa forma temos a interface pronto para ser usada, receber e exibir as informações de funcionários.
Acompanhe a segunda parte do artigo onde vamos implementar o código no arquivo MainPage.xaml.cs referente ao evento Click do botão Procurar Funci, que irá chamar a rotina carrega TodosFuncionarios - responsável por usar o serviço criado para obter as informações dos funcionários.
Eu sei é apenas SilverLight 4, mas eu gosto...
artigo publicado originalmente no iMasters, por José Carlos Macoratti