No último sábado dia 27/04 participei do Global Azure Bootcamp 2019 – Campinas, evento no qual ajudei na organização, palestrando e também ministrando um workshop (este último em parceria com o Robson Rocha). As incrições do evento foram efetuadas pelo Sympla e tivemos um publico de 70 pessoas.
Os slides utilizados na minha palestra, sobre como Desenvolver “offline” no Azure você confere logo abaixo:
E para quem quiser o código utilizado, é só baixar através desse link
Já sobre o Workshop dos 5 Serviços do Azure que todo o Desenvolvedor precisa conhecer, os slides você confere logo abaixo e o código utilizado através desse link
Na última sexta-feira dia 12/04 participei do MVP Conf Latam 2019 palestrando sobre os Cinco Serviços do Azure que todo desenvolvedor precisa conhecer.
Este evento contou com mais de 2.000 pessoas inscritas e todo o valor arrecadado na venda dos ingressos foi destinado a intituições de caridade, uma em cada região do Brasil.
Os slides utilizados na palestra você confere logo abaixo:
Deixo aqui também o código fonte utilizado através desse link.
No último sábado dia 09/03 participei do Open Source Roadshow 2019 – Mogi das Cruzes-SP, evento no qual ajudei tanto na organização quanto palestrando sobre como Hospedar sua aplicação na nuvem com o Azure App Service.
Os slides utilizados na palestra você confere logo abaixo:
E para quem quiser o container utilizado no exemplo é só baixar através desse link.
Deixo aqui também dois artigos que já publiquei como referência ao conteúdo apresentado.
Conforme vimos na primeira parte
deste artigo, entendemos brevemente o conceito de como funciona o
protocolo oAuth 2, o OpenID Connect e o Identity Server 4. Então, sem
mais delongas, vamos para a melhor parte, que é colocar tudo isso na
prática.
Show me the Code!
Primeiro vamos criar um novo projeto do tipo ASP.NET Core Web Application (File > New Project). Chamaremos ele de IdentityServer, e na tela seguinte escolheremos a opção Web Application (Model-View-Controller), conforme a figura 1, abaixo:
Feito isso, vamos adicionar o Identity Server4 utilizando o Nuget. Para isso, clique com o botão direito na opção Dependencies e selecione a opção Manage Nuget Packages. Em seguida, adicione o pacote do Identity Server4, conforme figura abaixo:
Agora, para facilitar nosso desenvolvimento, faremos o download do projeto Quickstart UI repo. Esse projeto nos dá uma interface pronta com todos os tipos de autenticação previamente desenvolvidos. Vamos agora copiar as pastas QuickStart, Views e wwwroot baixadas dentro do nosso projeto para que possamos, em seguida, iniciar o desenvolvimento do nosso servidor de identidade.
Depois de copiar todos os arquivos, poderemos deletar do nosso projeto as pastas Controllers e Models, pois não a utilizaremos mais. Com todas essas alterações feitas, seu projeto deve estar igual ao da Figura 3:
Agora vamos definir uma porta para que o nosso projeto execute. Para isso, botão direito em cima do projeto e clique em properties (se for Visual Studio for Mac, clique em options ao invés de properties), depois clique em build, e no campo App URL,
substitua a porta pra 5000 (ou qualquer outra da sua escolha – você só
precisa se lembrar dela depois), e então salve o projeto. Feito isso,
vamos criar uma classe chamada Config.cs e vamos implementar nossas configurações (comentários no próprio código para melhor entendimento):
using IdentityServer4;
using IdentityServer4.Models;
using IdentityServer4.Test;
using System.Collections.Generic;
using System.Security.Claims;
namespace IdentityServer
{
public class Config
{
// Aqui vamos definir os resources que serão utilizados no nosso servidor
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email()
};
}
// Aqui definimos qual Client (aplicação) que poderá acessar nosso servidor de identidade.
public static IEnumerable<Client> GetClients()
{
// Credenciais da Aplicação
return new List<Client>
{
// OpenID Connect
new Client
{
// O Nome ÚNICO da nossa aplicação autorizada no nosso servidor de autoridade
ClientId = "ericsonfApp",
// Nome de exibição da nossa aplicação
ClientName = "ericsonf Application",
//Tipo de autenticação permitida
AllowedGrantTypes = GrantTypes.Implicit,
//Url de redicionamento para quando o login for efetuado com sucesso.
RedirectUris = { "http://localhost:5001/signin-oidc" },
//Url de redirecionamento para quando o logout for efetuado com sucesso.
PostLogoutRedirectUris = { "http://localhost:5001/signout-callback-oidc" },
//Escopos permitidos dentro da aplicação
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email
}
}
};
}
// TestUser é uma classe de exemplo do proprio IdentityServer, aonde configuramos basicamente login/senha e as claims de exibição.
public static List<TestUser> GetUsers()
{
return new List<TestUser>
{
new TestUser
{
SubjectId = "1",
Username = "ericsonf",
Password = "ericsonf",
Claims = new List<Claim>
{
new Claim("name", "Portal ericsonf"),
new Claim("website", "https://ericsonf.com.br"),
new Claim("email", "contato@ericsonf.com")
}
}
};
}
}
}
O próximo passo agora é configurar o Startup.cs da nossa aplicação para que ele fique igual abaixo:
using IdentityServer4.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace IdentityServer
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Configuração do Identity Server In-Memory
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetUsers());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
//Habilitando o uso do Identity Server no nosso projeto
app.UseIdentityServer();
//Habilitando o uso de arquivos estáticos (Html, Css e etc) do nosso projeto
app.UseStaticFiles();
//Habilitando o uso de rota no projeto
app.UseMvcWithDefaultRoute();
}
}
}
Feito isso, configuramos o nosso servidor de Identidade, e agora podemos criar uma aplicação para testarmos a nossa autenticação. Então basicamente vamos seguir os mesmos passos que fizemos anteriormente ao criar um novo projeto do tipo ASP.NET Core Web Application, porém, esse vamos chamar de ericsonfApp. Mais uma vez nossa pasta Models não será utilizada em nosso exemplo, então podemos deletá-la do nosso projeto.
Antes de começarmos a programar nossa Aplicação, vamos primeiramente
configurar a porta em que a aplicação vai rodar, e seguindo os mesmos do
nosso servidor de identidade, vamos alterar a porta, porém dessa vez
vamos apontar para a porta 5001 (observe que no código do Config.cs, apontamos o Login e Logout da aplicação para a porta 5001 também).
Agora podemos começar a programar a nossa aplicação, então
primeiramente vamos abrir o arquivo Startup.cs (comentários no próprio
código para melhor entendimento):
using System.IdentityModel.Tokens.Jwt;
using IdentityServer4;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace ericsonfApp
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
//Exibe as claims de maneira mais "amigável"
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
//Adciona o serviço de autenticação
services.AddAuthentication(options =>
{
//Nosso esquema default será baseado em cookie
options.DefaultScheme = "Cookies";
//Como precisamos recuperar os dados depois do login, utilizamos o OpenID Connect que por padrão utiliza o escopo do Profile
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
//Aponta para o nosso servidor de autenticação
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
//Nome da nossa aplicação que tentará se autenticar no nosso servidor de identidade
//Observe que ela possui o mesmo nome da app que liberamos no nosso servidor de identidade
options.ClientId = "ericsonfApp";
options.SaveTokens = true;
//Adicionamos o scopo do e-mail para utilizarmos a claim de e-mail.
options.Scope.Add(IdentityServerConstants.StandardScopes.Email);
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//Habilitando o uso da autenticação do Identity Server no nosso projeto
app.UseAuthentication();
//Habilitando o uso de arquivos estáticos (Html, Css e etc) do nosso projeto
app.UseStaticFiles();
//Habilitando o uso de rota no projeto
app.UseMvcWithDefaultRoute();
}
}
}
O próximo passo agora será configurarmos nosso HomeController.cs:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication;
namespace MvcClient.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
//Somente será acessada se o usuário estiver logado
[Authorize]
public IActionResult Secure()
{
return View();
}
public async Task Logout()
{
//Limpa o cookie para finalizar o logout
await HttpContext.SignOutAsync("Cookies");
await HttpContext.SignOutAsync("oidc");
}
public IActionResult Error()
{
return View();
}
}
}
E por fim, vamos criar nossa página que vai exibir os dados caso o
usuário seja válido e se logue corretamente. Para isso, crie o arquivo Secure.cshml dentro da pasta Views > Home.
@{
ViewData["Title"] = "Logon efetuado com sucesso.";
}
<h2>@ViewData["Title"]</h2>
<h3>Claims autorizadas pelo usuário</h3>
<dl>
@foreach (var claim in User.Claims)
{
<dt>@claim.Type</dt>
<dd>@claim.Value</dd>
}
</dl>
Feito isso vamos colocar nosso Visual Studio para executar os dois
projetos simultaneamente, e vamos fazer o login com os dados de acesso
que criamos no nosso servidor de identidade. Se tudo deu certo você
obterá uma tela igual a figura 4 abaixo:
Agora é só selecionar quais informações deseja exibir e na próxima tela será exibido o resultado de ac.
Bônus
Como bônus, vou implementar um Resource customizado, pois nem sempre temos as Claims que desejamos “prontas”, então mãos a obra!
Primeiramente vamos alterar o método GetIdentityResources() que se encontra no arquivo Config.cs do projeto IdentityServer.
public static IEnumerable<IdentityResource> GetIdentityResources()
{
var customProfile = new IdentityResource(
name: "custom.profile",
displayName: "Custom Profile",
claimTypes: new[] { "customclaim" }
);
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
customProfile
};
}
Depois, vamos alterar a propriedade AllowedScopes:
Agora vamos adicionar nossa Claim customizada nas Claims do usuário:
Claims = new List<Claim> {
new Claim("name", "Portal ericsonf"),
new Claim("website", "http://ericsonf.com.br"),
new Claim("email", contato@ericsonf.com new Claim("customclaim", "Minha claim customizada")
}
E por ultimo, vamos abrir o arquivo Startup.cs do projeto ericsonfApp e colocar a linha abaixo logo após a inclusão do scopo do e-mail:
options.Scope.Add("custom.profile");
Executando o projeto novamente e fazendo o login corretamente, note que
apareceu o nosso Resource customizado nas opções de exibição, conforme
mostra a figura 5 abaixo:
Bom, pessoal, era isso que eu queria mostrar. Para quem quiser o código completo ele está no meu GitHub.
Neste artigo, pretendo mostrar como criar um servidor de identidade para autenticarmos nossas aplicações através dele.
Antes de “colocarmos a mão na massa”, vamos entender de maneira simplificada como funciona o protocolo OAuth 2, a camada OpenID Connect e por fim, o Identity Server 4 para tirarmos mais proveito desse artigo (caso desejem ir mais a fundo, deixarei o link da documentação de cada um no final desse artigo).
OAuth 2
O OAuth 2.0 é um protocolo de autorização aberto que permite que uma
aplicação acesse recursos protegidos de outra aplicação em nome de um
usuário sem expor sua senha.
Exemplificando o que foi dito acima, é basicamente você acessar o Medium utilizando sua conta do Facebook para se autenticar.
Com isso, temos algumas vantagens como, por exemplo:
Não precisamos mais criar nem salvar uma nova senha em cada aplicação de terceiros que formos utilizar
Posso revogar um acesso a qualquer momento sem deixar “dados” pra trás
O OAuth 2.0 estabelece quatro definições para nós, para que isso seja possível:
Resource Owner: o “dono do recurso”
Resource Server: servidor que possui os recursos protegidos
Authorization Server: servidor que gera um token que permite que o cliente acesse os recursos que o Resource Owner permitiu
Client: aplicação que acessa os recursos no Resource Server em nome do usuário
Abaixo, ilustrei as definições descritas acima para melhor entendimento
O Resource Owner através de um Client tenta acessar o Resource
Server (mantendo o exemplo anterior, vamos imaginar que seja o Medium);
Esse Resource Server “confia” em um Authorization Server (seguindo o mesmo exemplo, esse cara seria o Facebook);
O Resource Owner se autentica no Authorization Server;
O Authorization Server retorna um token de autenticação válido para o Client;
O Client acessa o Resource Server.
Bom, agora que entendemos o fluxo básico do funcionamento do protocolo OAuth 2.0, vamos para o OpenID Connect.
OpenID Connect
O OpenID Connect é uma camada de identidade bem simples, criado sob o
protocolo OAuth 2.0. Conforme dito acima, o OAuth 2.0 fornece um
mecanismo de autenticação que gerará um token de acesso para acessarmos
recursos protegidos em uma aplicação qualquer, porém, ele apenas
“autentica”, não nos fornecendo nenhuma informação de identidade do
usuário.
O OpenID Connect implementa uma “extensão” para o protocolo OAuth
2.0. É ele que vai nos fornecer informações sobre o usuário final na
forma de um id_token que verifica a identidade do usuário e fornece
informações básicas de perfil do mesmo.
Identity Server 4
O Identity Server 4 é um middleware feito na tecnologia .NET que
utiliza o Oauth 2 e o OpenID Connect, e tem como finalidade permitir que
diferentes aplicações (independentemente de tecnologia) “conversem
entre si”. Ele também permite a criação de Single Sign-On, proteção de
web API’s e gerenciamento de autorização de acesso.
No Identity Server 4 nós possuimos três definições, que seguem abaixo:
Users: é uma pessoa, de fato, que possua as credenciais necessárias para acessar o servidor de identidade;
Clients: aplicações autorizadas a acessar o meu
servidor de identidade, pois assim como os Users, eles vão possuir um
nome e/ou Id de acesso;
Scopes: são basicamente as informações que um Client deseja acessar no meu servidor de identidade
Bom, agora que já temos uma visão geral de como tudo funciona, na parte 2 desse artigo vamos começar a implementar o nosso servidor de identidade com o Identity Server 4 e com o Asp.Net Core 2.
Criada em meados de 2012 por Robert Cecil Martin (“Uncle Bob”), a
Clean Architecture tem como principais finalidades ser independente de
frameworks, facilmente testável, independentemente da interface do
usuário, ou seja, a interface do usuário pode mudar à vontade sem que
isso reflita no resto do sistema, ser independente de banco de dados, já
que ela mantém todas as regras de negócio na própria aplicação e, por
último, ser independente de qualquer agente externo, sendo que as regras
de negócio não “enxergam” o “mundo exterior”. Resumindo, a Clean
Architecture é um tipo de arquitetura de software amplamente
independente.
Neste artigo, mostraremos o conceito dessa arquitetura e também um exemplo de como implementá-la no mundo real.
Clean Architecture
Dependências
Na imagem acima, temos o desenho de como a Clean Architecture
funciona. Note que as setas horizontais da imagem que representam as
dependências entre as camadas vêm de “fora para dentro”, ou seja, a
camada Framework “enxerga” somente a Interface Adapters, que por sua vez
“enxerga” somente a User Cases, que finalmente “enxerga” apenas a
Entites. Esta é a principal regra da Clean Architecture, e talvez sua
única: o Princípio da Dependência.
As camadas internas não devem ter qualquer dependência das externas, nem indiretas, como nomes de variáveis, funções ou termos.
Provavelmente você terá mais abstrações nas camadas internas e mais
implementações nas externas, utilizando injeção de dependência para
fazer tudo funcionar.
Entities
Camada que encapsula as entidades e regras de negócio. O principal
entendimento aqui é que essa camada deve conter tudo que seja pertinente
ao sistema como um todo de uma maneira mais genérica, ou seja, deve
conter as regras que tenham menos possiblidades de mudança quando algo
externo mudar, como algo na interface do usuário.
Em um grande sistema, que reusa código da empresa, aqui poderiam
residir as bibliotecas que são consumidas pela aplicação, mas que a
aplicação não irá modificar, como algum framework ou outros domínios de
negócio. Em uma aplicação menor, podem ser apenas interfaces e classes
que tenham que ser utilizadas em todas as camadas, como a interface de
um repositório.
User cases
Essa camada é a que contém as regras de negócios mais específicas do
sistema. É aqui que todos os casos de uso do sistema são implementados. É
esperado que apenas mudanças de requisitos afetem essa camada, sendo
que assim como na Entities, alterações nas camadas externas não devem
afetar essa camada. (sugestão: essa camada. No entanto, assim como na
Entities, alterações…)
Interface Adapters
Camada que tem como finalidade converter dados da maneira mais
acessível e conveniente possível para as camadas Entities e User Cases.
Um exemplo seria o uso do AutoMapper, onde eu poderia controlar as
estruturas transmitidas entre User Cases e Entities com o interface do
usuário, por exemplo.
Frameworks
Essa camada é composta por ferramentas como banco de dados, interface
do usuário etc. Nessa camada, a ideia ée ter o mínimo de código
possível, apenas o necessário para interligar as camadas e injetar as
implementações necessárias nas camadas interiores.
Usando a Clean Architecture no mundo real
Para facilitarmos o entendimento, criamos um exemplo bem simples de
um sistema escolar, no qual vamos cadastrar novas turmas. A estrutura
base do projeto foi divida nas camadas: Core, Entities, UseCases e Web,
conforme nos mostra a imagem abaixo:
A camada Core foi incluída a fim de ter um nível de abstração ainda
maior do que a Entities. Essa camada possui apenas um repositório padrão
(IRepository), um interactor que vai receber uma entrada e nos devolver
uma resposta (IInteraction) e uma entidade que possui as propriedades
que serão utilizadas por todas as classes (IEntity). Outra coisa
interessante nessa camada é que ela contém todos os pacotes que serão
utilizados pelas outras camadas, já que, conforme dito anteriormente, as
dependências são de “fora para dentro”.
Na camada Entities, temos as Classes que de fato compõem o sistema –
observe que ela conhece a camada Core, uma vez que ela é menos abstrata.
Na camada UseCases, possuímos todas as nossas regras de negócio.
Observe que temos apenas três arquivos para a entidade Turma, sendo um
arquivo de Input que vai receber os dados (CadastrarTurmaInput), um
Interactor (CadastrarTurmaInteractor) que contém todas as regras de
negócio implementadas – por exemplo, ao cadastrar uma Turma será que
existe um professor disponível? Ou será que já não existe uma turma para
mesma data? – e por último um arquivo de output (CadastrarTurmaOutput)
que vai transformar os dados para que possa ser exibido. (possam ser
exibidos? Fiquei em dúvida se isso se refere a dados)
Nossa última camada, a Web, vai apenas consumir os dados
transformados pela camada anterior e exibi-los para o usuário. Ela
também é responsável, nesta implementação simplificada, por injetar os
repositórios para consumo nos interactors, e os interactors para consumo
pelos controllers.
Conforme vimos no exemplo do sistema escolar, cada camada possui uma
única responsabilidade sempre respeitando suas dependências, mantendo
assim uma divisão total de responsabilidades, minimizando ao máximo
qualquer alteração externa com interface com o usuário (de usuário?),
banco de dados etc.
Caso queiram baixar o código utilizado no exemplo, ele pode ser encontrado através deste link.
Se você já
teve e/ou tem bastante vivência com a linguagem T-SQL e hoje se depara com o um
software que tenha um ORM para fazer as transações com o banco de dados, pode
ser que aparecem algumas dúvidas sobre como “converter” uma querie T-SQL na
“linguagem” que o ORM entende.
Se você se
deparar com essa situação e tiver alguma dúvida quanto a alguma consulta que
precisa converter, esse post é destinado a você.
Nesse post
vamos utilizar o Entity Framework como ORM e a linguagem LINQ to Entities que
permite aos desenvolvedores escreverem consultas nesse modelo usando VB ou C#.
Abaixo
segue o modelo que utilizei para facilitar o entendimento das consultas.
SELECT COM UM TOP
T-SQL: SELECT TOP 2 Nome FROM Artista;
LINQ: var result = (from a in context.Artista select a.Nome).Take(2);
LAMBDA: var result = context.Artista.Select(a => a.Nome).Take(2);
SELECT COM UM WHERE
T-SQL: SELECT Nome FROM Artista WHERE Nome = ‘Led Zeppelin’;
LINQ: var result = (from a in context.Artista
where a.Nome.Equals(“Led Zeppelin”)
select a.Nome).ToList();
LAMBDA: var result = context.Artista
.Where(a => a.Nome.Equals(“Led Zeppelin”))
.Select(a => a.Nome).ToList();
SELECT COM UM INNER JOIN
T-SQL: SELECT a.Nome, al.Titulo FROM Artista a
INNER JOIN Album al ON al.ArtistaId = a.ArtistaId;
LINQ: var result = (from a in context.Artista
join al in context.Album on a.ArtistaId equals al.ArtistaId
select new
{
a.Nome,
al.Titulo
}).ToList();
LAMBDA: var result = context.Artista
.Join(context.Album, artista => artista.ArtistaId, album => album.ArtistaId,
(artista, album) => new { Artista = artista, Album = album })
.Select(a => new {
Nome = a.Artista.Nome,
Titulo = a.Album.Titulo
}).ToList();
SELECT COM DUAS CONDIÇÕES NO MESMO INNER JOIN
T-SQL: SELECT DISTINCT a.Nome, al.Titulo FROM Artista a
INNER JOIN Album al ON al.ArtistaId = a.ArtistaId AND al.Titulo = a.Nome;
LINQ: var result = (from a in context.Artista
join al in context.Album on
new { C1 = a.ArtistaId, C2 = a.Nome } equals
new { C1 = al.ArtistaId, C2 = al.Titulo }
select new
{
a.Nome,
al.Titulo
}).ToList();
LAMBDA: var result = context.Artista
.Join(context.Album, artista => new { C1 = artista.ArtistaId, C2 = artista.Nome },
album => new { C1 = album.ArtistaId, C2 = album.Titulo },
(artista, album) => new { Artista = artista, Album = album })
.Select(a => new {
Nome = a.Artista.Nome,
Titulo = a.Album.Titulo
}).ToList();
SELECT COM DOIS INNER JOINS
T-SQL: SELECT a.Nome, al.Titulo, f.Nome FROM Artista a
INNER JOIN Album al ON al.ArtistaId = a.ArtistaId
INNER JOIN Faixa f ON f.AlbumId = al.AlbumId;
LINQ: var result = (from a in context.Artista
join al in context.Album on a.ArtistaId equals al.ArtistaId
join f in context.Faixa on al.AlbumId equals f.AlbumId
select new
{
Artista = a.Nome,
Album = al.Titulo,
Faixa = f.Nome
}).ToList();
LAMBDA: var result = context.Artista
.Join(context.Album, artista => artista.ArtistaId, album => album.ArtistaId,
(artista, album) => new { Artista = artista, Album = album })
.Join(context.Faixa, album => album.Album.AlbumId, faixa => faixa.AlbumId,
(album, faixa) => new { Album = album, Faixa = faixa })
.Select(a => new {
Nome = a.Album.Artista.Nome,
Titulo = a.Album.Album.Titulo,
Faixa = a.Faixa.Nome
}).ToList();
SELECT COM UM LEFT JOIN
T-SQL: SELECT a.Nome, al.Titulo FROM Artista a
LEFT JOIN Album al ON al.ArtistaId = a.ArtistaId;
LINQ: var result = (from a in context.Artista
join al in context.Album on a.ArtistaId equals al.ArtistaId
into All from al in All.DefaultIfEmpty()
select new
{
a.Nome,
al.Titulo
}).ToList();
LAMBDA: var result = context.Artista
.GroupJoin(context.Album, artista => artista.ArtistaId, album => album.ArtistaId,
(artista, album) => new { Artista = artista, Album = album.DefaultIfEmpty() })
.SelectMany(a => a.Album, (a, album) => new {
a.Artista.Nome,
album.Titulo
}).ToList();
SELECT COM DOIS LEFT JOINS, GROUP BY e COUNT
T-SQL: SELECT DISTINCT a.Nome, (SELECT COUNT(*) AS Album FROM Album WHERE ArtistaId = a.ArtistaId) AS Album,
COUNT(f.FaixaId) AS Faixa
FROM Artista a
LEFT JOIN Album al ON al.ArtistaId = a.ArtistaId
LEFT JOIN Faixa f ON f.AlbumId = al.AlbumId
GROUP BY a.Nome, a.ArtistaId;
LINQ: var result = (from a in context.Artista
join al in context.Album on a.ArtistaId equals al.ArtistaId
into All1 from al in All1.DefaultIfEmpty()
join f in context.Faixa on al.AlbumId equals f.AlbumId
into All2
from f in All2.DefaultIfEmpty()
group a by a into grouped
let totalAlbuns = grouped.Key.Album.Count()
let totalFaixas = grouped.Count(x => x.Album.Count > 0)
select new
{
Artista = grouped.Key.Nome,
Albuns = totalAlbuns,
Faixas = totalFaixas
}).ToList();
LAMBDA: var result = context.Artista
.GroupJoin(context.Album, artista => artista.ArtistaId, album => album.ArtistaId,
(artista, album) => album.Select(x => new {
Artista = artista,
Album = x
})
.DefaultIfEmpty(new {
Artista = artista,
Album = (Albuns)null
}))
.SelectMany(x => x)
.GroupJoin(context.Faixa,
album => album.Album.AlbumId,
faixa => faixa.Album.AlbumId,
(album, faixa) => faixa.Select(y => new {
Artista = album.Artista,
Album = album.Album,
Faixa = y
})
.DefaultIfEmpty(new {
Artista = album.Artista,
Album = album.Album,
Faixa = (Faixas)null
}))
.SelectMany(y => y)
.GroupBy(grouped => new { grouped.Artista })
.Select(g => new
{
Artista = g.Key.Artista.Nome,
Albuns = g.Key.Artista.Album.Count(),
Faixas = g.Count(x => x.Album.Faixa.Count > 0)
}).ToList();
Como
vocês puderam ver a ideia desse artigo foi de ser o mais direto ao ponto
possível. Obviamente, é impossivel cobrir todos os cenários possíveis de se
fazer consultas T-SQL, então tentei colocar
desde um exemplo simples até um exemplo um pouco mais complexo.
Antes de irmos diretamente para o código, vamos entender brevemente o que é serialização e deserialização.
Serializar é o processo de transformar um objeto que você possui em um stream de bytes ou de texto. Deserializar por sua vez, é exatamente o oposto, é você converter um stream de bytes ou de texto em um objeto novamente.
Para nos ajudar nesse processo, o .NET Framework nos oferece algumas classes que podem ser encontradas nos namespaces System.Runtime.Serialization e System.Xml.Serialization, além de nos oferecer por padrão três mecanismos serialização/deserialização:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public int Age;
public Person() { }
public Person(int id, string firstName, string lastName, string email, int age)
{
Id = id;
FirstName = firstName;
LastName = lastName;
Email = email;
Age = age;
}
}
BinaryFormatter
XmlSerializer
DataContractSerializer
Logo mais nesse artigo veremos como utilizar cada um desses mecanismos na prática, portanto usaremos a classe Person, que se encontra logo abaixo como base para nossos exemplos.
Binary Serialization
A serailização Binária, como o próprio nome já sugere, serializa um objeto ou dado para o formato binário. Esse tipo de serialização armazena não somente os dados, mas também o tipo de objeto que esta sendo serializado.
Para utilizarmos esse tipo de serialização, devemos marcar nossa classe como o atributo [Serializable], sendo que todos os campos podem ser serializados, inclusive os marcados como private, portanto caso queiramos impedir que algum campo da nossa classe não seja serializado, devemos marca-la com o atributo [NonSerialized], também é importante ressaltarmos que propriedades serão sempre serializadas e que nenhum construtor é executado nesse tipo de serialização.
Outra característica importante é que esse tipo de serialização também é “mais rigorosa” que as demais, se o serializador binário não achar algum campo por exemplo, ele retornará uma Exception. No caso de uma atualização de versão por exemplo, você pode adicionar o [OptionalFieldAttribute] no novo campo adicionado para que a serialização binária entenda que esse campo não existia antes, por exemplo.
//Criamos uma nova pessoa para serializarmos
var person = new Person(1, "João", "da Silva", "joao.silva@xpto.com", 50);
//Instanciamos a classe BinarryFormatter
var binaryFormatter = new BinaryFormatter();
//Criamos um arquivo .bin que receberá os dados serializados do objeto person
using (var fileStream = new FileStream("ericsonf.bin", FileMode.Create))
{
binaryFormatter.Serialize(fileStream, person);
}
Console.WriteLine("Serialização Concluida");
//Abrimos o arquivo serializado anteriormente para podermos deserializa-lo.
using (var fileStream = new FileStream("ericsonf.bin", FileMode.Open))
{
var p = (Person)binaryFormatter.Deserialize(fileStream);
}
Console.WriteLine("Deserialização Concluida");
XML Serialization
Ao contrário do modelo de serialização anterior, o XML é um tipo de dado legivel também para seres humanos, e é fracamente acoplado, ou seja, se você adicionar propriedades ou campos ao seu objeto a ser serializado ele não notará.
Assim como na serialização binária, para serializamos, precisamos marcar o objeto como o atributo [Serializable],
entretanto, aqui somente os campos e/ou propriedades publicas são
serializadas. Caso queiramos que um campo e/ou propriedade não seja
serializado, devemos marca-lo com o atributo [XmlIgnore]. Vale lembrar que propriedades também podem ser ignoradas e não somente campos, além de que, na serialização XML se faz necessário ter um construtor vazio na classe diferentemente do modelo de serialização anterior.
//Criamos uma nova pessoa para serializarmos
var person = new Person(1, "João", "da Silva", "joao.silva@xpto.com", 50);
//Instanciamos a classe XmlSerializer, e "dizemos" a ele que tipo de objeto vamos serializar.
var xml = new XmlSerializer(typeof(Person));
//Criamos um arquivo .xml que receberá os dados serializados do objeto person
using (var fileStream = new FileStream("ericsonf.xml", FileMode.Create))
{
xml.Serialize(fileStream, person);
}
Console.WriteLine("Serialização Concluida");
//Abrimos o arquivo serializado anteriormente para podermos deserializa-lo.
using (var fileStream = new FileStream("ericsonf.xml", FileMode.Open))
{
var p = (Person)xml.Deserialize(fileStream);
}
Console.WriteLine("Deserialização Concluida");
Data Contract
O Data Contract Serializer é utilizado pelo WCF para serializar seus objetos tanto em XML quanto em JSON. Esse tipo de serialização é feita pelas classes DataContractSerializerpara XML e DataContractJsonSerializer para JSON (discutido mais adiante).
Nesse modelo eu preciso marcar meu objeto a ser serializado com o atributo [DataContract],
além de que nesse modelo ao contrario dos outros, eu preciso marcar
todos os membros que eu desejo serializar explicitamente com o atributo [DataMember], caso eu deseje ignorar algum membro, devemos marca-lo com o atributo [IgnoreDataMember] ou se preferir, apenas deixa-lo sem a marcação do atributo [DataMember]. Outra caracteristica importante é que nesse modelo, campos privados também pode ser serializados.
//Criamos uma nova pessoa para serializarmos
var person = new Person(1, "João", "da Silva", "joao.silva@xpto.com", 50);
//Instanciamos a classe DataContractSerializer, para serializarmos em XML e "dizemos" a ele que tipo de objeto vamos serializar.
var dataContract = new DataContractSerializer(typeof(Person));
//Criamos um arquivo .xml que receberá os dados serializados do objeto person
using (var fileStream = new FileStream("ericsonf.xml", FileMode.Create))
{
dataContract.WriteObject(fileStream, person);
}
Console.WriteLine("Serialização Concluida");
//Abrimos o arquivo serializado anteriormente para podermos deserializa-lo.
using (var fileStream = new FileStream("ericsonf.xml", FileMode.Open))
{
var p = (Person)dataContract.ReadObject(fileStream);
}
Console.WriteLine("Deserialização Concluida");
JSON Serialization
Utiliza-se também o Data Contract Serializer e suas respectivas caracteristicas para que a serilização seja efetuada, porém com uma única alteração, que ao invés de eu utilizar a classe DataContractSerializer eu utilizo a classe DataContractJsonSerializer para que possamos serializar em JSON.
//Criamos uma nova pessoa para serializarmos
var person = new Person(1, "João", "da Silva", "joao.silva@xpto.com", 50);
//Instanciamos a classe DataContractJsonSerializer, para serializarmos em JSON e "dizemos" a ele que tipo de objeto vamos serializar.
var dataContract = new DataContractJsonSerializer(typeof(Person));
//Criamos um arquivo .xml que receberá os dados serializados do objeto person
using (var fileStream = new FileStream("ericsonf.json", FileMode.Create))
{
dataContract.WriteObject(fileStream, person);
}
Console.WriteLine("Serialização Concluida");
//Abrimos o arquivo serializado anteriormente para podermos deserializa-lo.
using (var fileStream = new FileStream("ericsonf.json", FileMode.Open))
{
var p = (Person)dataContract.ReadObject(fileStream);
}
Console.WriteLine("Deserialização Concluida");
Bom
pessoal, a ideia desse artigo era mostrar a vocês as particularidades e
exemplos de como utilizar cada dos modelos de serialização utilizando
C#.
Espero que tenham gostado do artigo e até a próximo.