Deploy .NET Core 6 Application no Azure Web App com GitHub Actions

Olá, pessoal, tudo bem?

Nesse post vou mostrar como publicar um aplicação feita em .NET Core 6 no Azure Web App utilizando GitHub Actions.

A aplicação que faremos deploy pode ser encontrada nesse link e ela consiste em calcular o Fatorial de um número utilizando Recursão como solução para o problema. Aproveitando, vou deixar abaixo uma citação muito interessante sobre recursão.

“Os loops podem melhorar o desempenho do seu programa. A recursão melhorar o desempenho do seu programador. Escolha o que for mais importante para a sua situação.”

Leigh Coldwell (Retirado do livro: Entendendo Algoritmos)

Sem mais delongas, vamos para o passo a passo.

Primeiramente, vamos acessar o portal do Azure e criar um Web App.

  1. Clique em Create a Resource e na barra de pesquisa digite Web App em seguida clique em Create.
  2. Agora vamos preencher os dados necessários para criar nosso Web App:
    1. Selecione ou crie um Resource Group;
    2. Digite um nome para sua Web App (lembrando que esse nome precisa ser único);
    3. No Runtime stack, selecione .NET 6 (LTS);
    4. Em Operating System, selecione Linux;
    5. Em Region, selecione a região que mais lhe agrada;
    6. Em SKU and Size selecione o plano que deseja (existe um plano Free);
    7. E finalize clicando em Next:Deployment;
  3. Nesse momento começa a configuração do Deploy contínuo utilizando GitHub Actions.
    1. Em Continuous deployment, marque Enable;
    2. Faça o login da sua conta GitHub;
    3. Na sequência, selecione a Organização, o Repositório e a Branch que deseja que ocorra o deploy;
  4. Agora clique em Review + Create (Poderíamos customizar mais configurações no nosso Web App, entretanto como o propósito desse artigo é demonstrar como fazer o deploy, ja vamos finalizar nossa customização aqui).

Assim que terminar o deploy, podemos clicar no link (https://<nome_do_seu_webapp>.azurewebsites.net) para acessar a nossa aplicação. Feito isso observe que ocorre um HTTP ERROR 404, entretanto veja que nossa aplicação esta funcionando, e para confirmar isso basta fazer um curl na API, conforme exemplo abaixo:

curl -X 'GET' \                                                                                 127 ✘  15:53:27 
  'https://web-factorial.azurewebsites.net/Factorial?number=5' \
  -H 'accept: */*'
The factorial of the number 5 is 120.%

Para deixarmos algo mais amigável, note que o deploy esta considerando o environment como Produção e no nosso código por padrão (e principalmente, por segurança), o Swagger só esta habilitado para ambiente de desenvolvimento, então se voltamos ao nosso código no arquivo Program.cs e comentar o seguinte trecho de código:

//if (app.Environment.IsDevelopment())
//{
    app.UseSwagger();
    app.UseSwaggerUI();
//}

E na sequência, comitarmos, observe que automaticamente o Deploy sera executado, e ao seu término o Swagger estará disponível, conforme figura abaixo:

Figura 1

E assim encerramos esse artigo. Espero que tenham gostado e até a próxima.

Muito Obrigado!

SOLID Principles

Olá pessoal, tudo bem?

Nesse post vou colocar alguns exemplos (todos escritos em C#) dos princípios SOLID do Uncle Bob.

S – Single Responsibility Principle

Em breve…

O – Open Closed Principle

Você deve ser capaz de estender um comportamento de uma classe sem a necessidade de modificá-lo.

Exemplo prático

L – Liskov Substitution Principle

As classes derivadas devem ser substituíveis por suas classes bases.

Exemplo prático

I – Interface Segregation Principle

Crie interfaces granulares a específicas para os seus clientes.

Exemplo prático

D – Dependency Inversion Principle

Abstrações não devem depender de detalhes. Os detalhes devem depender das abstrações.

Exemplo prático

Espero que tenham gostado e até a próxima.

Obrigado!

Redimensionando imagens com Azure Functions e Azure Storage

Olá pessoal, tudo bem? Nesse post vamos aprender como fazer um redimensionamento de imagens altamente escalável e muito simples utilizando Azure Functions e Azure Storage.

Primeiramente vamos criar uma conta de Storage no Azure e na sequência 2 containers no serviço de Blob. O primeiro container vai se chamar images (onde nossa Function vai monitorar), e o segundo vai se chamar thumbnails (onde salvaremos a nossa imagem redimensionada).

Dica: Caso esteja utilizando o sistema operacional Windows, você pode utilizar o Azure Storage Emulator para simular o serviço de Storage do Azure.

Logo depois de criarmos o serviço de Storage (seja no Azure ou simulando localmente através do Azure Storage Emulator), vamos abrir o Terminal (Ctrl + ‘) do Visual Studio Code, e digitarmos:

func init

Note que vão aparecer 4 opções de seleção, nesse caso, selecione a opção “dotnet”, e então, espere que termine a criação do nosso Function App.

Logo depois, nosso próximo passo será o de criar nossa Azure Function, e para isso, utilize o comando abaixo:

func new

Agora, dentre as diversas opções de Azure Functions, vamos escolher o tipo BlobTrigger e daremos a ela o nome de ImageResizerTrigger.

Seguindo, abra o arquivo .csproj do seu projeto e adicione o package da SixLabors.ImageSharp nele, igual a linha abaixo:

<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-rc0001" />

Na sequência o Visual Studio Code pedirá para que você faça um Restore na aplicação.

Assim que o Restore finalizar, abra o arquivo ImageResizerTrigger.cs e substitua-o com o código abaixo:

public static void Run([BlobTrigger("images/{name}", Connection = "AzureWebJobsStorage")]Stream myBlob, string name, 
            [Blob("thumbnails/{name}", FileAccess.Write)] Stream outputBlob, ILogger log)
        {
            log.LogInformation($"C# Blob trigger function Processed blob\n Name:{name} \n Size: {myBlob.Length} Bytes");

            using (var image = Image.Load(myBlob))
            {
                var resizeOptions = new ResizeOptions
                {
                    Size = new Size(640, 480),
                    Compand = true,
                    Mode = ResizeMode.Max
                };

                image.Mutate(ctx => ctx.Resize(resizeOptions));
                image.Save(outputBlob, new JpegEncoder { Quality = 80 });
            }
        }

Dessa forma, note no nosso código, que a nossa Azure Function estará sempre olhando para o container images, e seu “output” será sempre no container thumbnails.

Importante: A propriedade Connection esta “setada” com o valor: AzureWebJobsStorage. Esse valor na verdade esta contido no arquivo local.settings.json e você deverá colocar a string de conexão do seu Storage no mesmo.

Assim, faça o upload de uma imagem no container images e note que a imagem será automaticamente redimensionada e salva no container thumbnails, conforme nós queríamos.

Por fim, caso tenha interesse em mais conteúdos relacionados a Azure Functions e Azure Storage, convido você a dar uma olhada na série: Criando um CRUD Serverless com Azure Functions.

Espero que tenham gostado e até a próxima. Obrigado!

Criando um CRUD Serverless com Azure Functions – Parte 4

Olá pessoal, tudo bem? Neste post derradeiro sobre como criar um CRUD Serverless com Azure Functions, vamos aprender como orquestrar nossas API’s utilizando o Azure API Management. O intuito desse post não é explicar em detalhes esse serviço, mas sim em focar como podemos deixar nossas API’s mais seguras.

Primeiramente, acesse o https://portal.azure.com e clique em Create a Resource, e logo na sequência na barra de busca pesquise por API Management.

Preencha as informações necessárias e aguarde a criação do serviço terminar (isso leva em média 20 minutos). Agora nosso próximo passo será importar nossas API’s e para isso na barra esquerda clique em API’s e em seguida selecione Function App. No modal que aparecer clique em Browse, depois em Function App e então selecione a aplicação Serverless que publicamos na parte 3 do artigo, e após o preenchimento dos dados clique em Create.

Observe que você pode criar um sufixo para a chamada das API’s editando o campo API URL suffix. Outro ponto interessante é que ele nos mostra todas as nossas API’s (HttpTriggers) e quais desejamos importar (por padrão todas vem selecionadas).

Dessa forma, terminada a importação, vamos adicionar um “Produto” para a nossa API. Por padrão temos duas opções: Starter e Unlimited, tendo a possibilidade criarmos um customizado também caso necessário.

Após selecionado o produto, vamos clicar em Developer portal e logo abaixo do nome de login (provavelmente ADMINISTRATOR), selecione a opção Profile. Note que clicando nessa opção apareceram os dois produtos disponíveis (Starter e Unlimited) como duas Keys para cada um. Essas keys serão fundamentais para que nossas API’s funcionem pois sem elas, se tentarmos acessa-las aparecerá o seguinte erro:

{
  "statusCode": 401,
  "message": "Access denied due to invalid subscription key. Make sure to provide a valid key for an active subscription."
}

Então nosso próximo passo será autorizar a chamada das nossas API’s e para isso será necessário passar um Header da requisição chamado Ocp-Apim-Subscription-Key e como valor a Key do produto em questão. Note que se você fizer a chamada de alguma API ela vai funcionar perfeitamente.

Agora vamos implementar algumas Policies para nossa aplicação. Habilitaremos o CORS, na sequência as chamadas para um determinado range de IP’s e por fim restringir um numero abusivo de chamadas as nossas API’s em um pequeno espaço de tempo.

Para isso selecione nosso conjunto de API’s e clique em base nas opções de Inbound processing e em seguida adicione o código abaixo para habilitar o CORS (lembre-se que todo o código deve ser colocado dentro da tag de inbound):

<cors>
    <allowed-origins>
        <origin>{SUA_URL}</origin>
    </allowed-origins>
    <allowed-methods>
        <method>*</method>
    </allowed-methods>
    <allowed-headers>
        <header>*</header>
    </allowed-headers>
    <expose-headers>
        <header>*</header>
    </expose-headers>
</cors>

Agora vamos liberar apenas um range de IP:

<ip-filter action="allow">
    <address-range from="<IP_INICIO>" to="<IP_FIM>" />
</ip-filter>

Por último, com o código abaixo, restringiremos por 15 segundos o acesso as nossas API’s caso alguém acesse alguma delas 3 vezes seguidas em sequência.

<rate-limit-by-key calls="3" renewal-period="15" counter-key="@(context.Request.IpAddress)" increment-condition="@(context.Response.StatusCode == 200)" />

Bom pessoal e assim finalizamos nossa série Criando um CRUD Serverless com Azure Functions.

E para finalizar deixo aqui um convite.

Que tal aprender mais sobre Docker, Kubernetes e a implementação de soluções baseadas em containers utilizando o Microsoft Azure, em um workshop que acontecerá durante um sábado todo (dia 04/04) em São Paulo Capital e implementando um case na prática?

Acesse então o link a seguir para efetuar sua inscrição (já incluso camiseta, emissão de certificado e almoço para os participantes) com o desconto: http://bit.ly/anp-docker-blog-ericson

Espero que tenham gostado e até a próxima.

Obrigado!

Criando um CRUD Serverless com Azure Functions – Parte 3

Olá pessoal, tudo bem?

Seguindo a nossa série em que vamos criar um CRUD Serverless com Azure Functions, hoje desenvolveremos a listagem dos nossos registros, a publicação da nossa Function App no Azure e por fim a publicação de um site estático consumindo nossas API’s no Azure Storage.

Primeiramente vamos trabalhar na listagem dos registros, e portanto, começaremos criando nossa Function através do comando abaixo:

func new

Feito isso, como já é sabido, aparecerão diversas opções de Azure Functions, cada uma para um propósito diferente, e no nosso caso escolheremos o tipo HttpTrigger e então nós daremos a ela o nome de ListPeople.

Abra o arquivo criado (ListPeople.cs) e vamos altera-lo com o código abaixo:

public static async Task<IEnumerable<Person>> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
    [Table("Person")]CloudTable cloudTable,
    ILogger log)
{
    log.LogInformation("ListPeople function started a request.");

    var tableQuery = new TableQuery<Person>();
    TableContinuationToken continuationToken = null;

    TableQuerySegment<Person> tableQueryResult;
    do
    {
        tableQueryResult = await cloudTable.ExecuteQuerySegmentedAsync(tableQuery, continuationToken);
        continuationToken = tableQueryResult.ContinuationToken;
    } while (continuationToken != null);

    log.LogInformation("ListPeople function finished a request.");

    return tableQueryResult.Results;
}

Observe que o código mudou levemente em relação as HttpTriggers anteriores. Nesse código mudamos o retorno da Função, sendo que agora ela retorna um IEnumerable de Person e para que isso seja possível utilizamos o comando TableQuerySegment para trazer todos os registros da nossa tabela.

Vamos agora executar nosso Function App utilizando o comando abaixo:

func start

Agora vamos testar nossa função de listagem de registros (para isso recomendo utilizar um Client REST da sua preferência) acessando o novo endpoint criado: ListPeople: [GET] http://localhost:7071/api/ListPeople, e observe ele retornou todos os registros cadastrados.

Ao contrário do que vimos na parte 1 e parte 2 do artigo, não criaremos nenhuma QueueTrigger, pois não iremos enfileirar nenhuma consulta, ao invés disso, vamos criar mais uma HttpTrigger e dessa vez será para trazer um único registro ao invés da listagem toda.

Novamente vamos utilizar o comando abaixo:

func new

Feito escolheremos novamente o tipo HttpTrigger e para essa função daremos o nome de GetPerson.

Abra o arquivo criado (GetPerson.cs) e vamos altera-lo com o código abaixo:

public static async Task<Person> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req,
    [Table("Person")]CloudTable cloudTable,
    ILogger log)
{
    log.LogInformation("GetPerson function started a request.");

    var partitionKey = "Person";
    var rowKey = req.Query["id"];

    TableOperation person = TableOperation.Retrieve<Person>(partitionKey, rowKey);
    TableResult result = await cloudTable.ExecuteAsync(person);

    log.LogInformation("GetPerson function finished a request.");

    return (Person)result.Result;
}

Note que o código acima não é bem semelhante ao que já produzimos nas Funções anteriores e então vamos mais uma vez testar a nossa aplicação, utilizando o comando:

func start

Mais uma vez vamos acessar o novo endpoint criado: GetPerson: [GET] http://localhost:7071/api/GetPerson, (lembre-se de passar o RowKey via QueryString sob o parâmetro de nome id ), e observe ele retornou o registro cadastrado.

Agora finalmente vamos publicar tudo isso no Azure, e para isso (caso ainda não tenha instalado), vamos instalar a extensão do Azure Functions no VsCode e com ela instalada, você irá clicar na opção Deploy to Function App…, e em seguida selecione sua Subscription no Azure (caso possua mais de uma), nosso próximo passo será selecionar Create a New Function App in Azure, feito isso ele irá lhe pedir um nome (necessita ser único globalmente), e por fim selecione a Region em que deseja fazer o deploy.

Agora é so aguardar alguns minutos para que o deploy seja finalizado no Azure.

Bônus

Para deixarmos nosso CRUD mais profissional podemos criar uma Single Page Application utilizando Angular e publicar isso no Azure, gastando apenas o custo de armazenamento, sem que seja cobrado qualquer custo de computação. Para realizarmos esse próximo passo, deixo abaixo um vídeo que eu publiquei no canal Azure na Prática fazendo um passo a passo de como fazer isso.

Por sim para quem tiver interesse todo o código da nossa Function App encontra-se no meu GitHub, inclusive contendo uma Single Page Application feita em Angular, pronta para ser publicada. Lembrando que no próximo e último artigo da série de posts, vou demonstrar como utilizarmos o Azure API Management pra a orquestração das nossas API’s criadas na nossa Function App.

E para finalizar deixo aqui um convite.

Que tal aprender mais sobre Docker, Kubernetes e a implementação de soluções baseadas em containers utilizando o Microsoft Azure, em um workshop que acontecerá durante um sábado todo (dia 04/04) em São Paulo Capital e implementando um case na prática?

Acesse então o link a seguir para efetuar sua inscrição (já incluso camiseta, emissão de certificado e almoço para os participantes) com o desconto: http://bit.ly/anp-docker-blog-ericson

Até mais pessoal.

Abraços e Obrigado!

Criando um CRUD Serverless com Azure Functions – Parte 2

Olá pessoal, tudo bem?

Seguindo a nossa série em que vamos criar um CRUD Serverless com Azure Functions, hoje desenvolveremos a parte de edição e de exclusão do nosso registro.

Primeiramente vamos trabalhar na edição, e portanto, começaremos criando nossa Function através do comando abaixo:

func new

Feito isso, como já é sabido, aparecerão diversas opções de Azure Functions, cada uma para um propósito diferente, e no nosso caso escolheremos o tipo HttpTrigger e então nós daremos a ela o nome de EditPerson.

Abra o arquivo criado (EditPerson.cs) e vamos altera-lo com o código abaixo:

public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "put", Route = null)]HttpRequest req,
    [Queue("EditPerson", Connection = "AzureWebJobsStorage")]IAsyncCollector<string> queueItem,
    ILogger log)
{
    log.LogInformation("EditPerson function started a request.");

    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    await queueItem.AddAsync(requestBody);

    log.LogInformation("EditPerson function finished a request.");

    return new OkObjectResult($"Obrigado! Seu registro já esta sendo processado.");
}

Observe que o código não é muito diferente do código da nossa HttpTrigger que criamos na parte 1 do nosso post.

Dica: Observe que na chamada da nossa HttpTrigger existe uma propriedade chamada Route, ela serve para customizarmos a rota da nossa Function caso você queira, do contrário o padrão será sempre o nome da sua HttpTrigger.

Vamos agora executar nosso Function App utilizando o comando abaixo:

func start

Note que agora nós temos dois endpointsCreatePerson: [POST] http://localhost:7071/api/CreatePerson e EditPerson: [PUT] http://localhost:7071/api/EditPerson e com isso já é possível testar nossa função de edição (para isso recomendo utilizar um Client REST da sua preferência), porém dessa vez precisaremos passar além das propriedades Name e Email as propriedades para encontrar o registro em si e elas são a PartitionKey e o RowKey. Com isso seu body deve ficar muito parecido com que se encontra logo abaixo:

{
	"name": "Blog do Ericson - Edit",
	"email": "ericson@email.com",
        "partitionKey": "Person",
        "rowKey": "<GUID_DO_REGISTRO>"
}

Feito isso abra a ferramenta Microsoft Azure Storage Explorer e localize dentro de Storage Account o serviços de Queues e abrindo-o note que foi criada uma Queue chamada EditPerson. Abra a mesma e note que o body que passamos para o nosso endpoint estará armazenado na fila.

Agora nosso próximo passo, assim como na parte 1 do nosso post, será o de desenfileirar a mensagem e salva-la no nosso banco de dados e para isso vamos criar nossa próxima Azure Function, e para isso, novamente vamos utilizar o comando abaixo:

func new

Dessa vez escolheremos o tipo QueueTrigger e colocaremos o nome dela de EditPersonQueue e em seguida, vamos altera-la com o código abaixo:

public static void Run(
    [QueueTrigger("EditPerson", Connection = "AzureWebJobsStorage")]string queueItem,
    [Table("Person")]CloudTable cloudTable,
    ILogger log)
{
    log.LogInformation($"EditPersonQueue trigger function started.");

    var data = JsonConvert.DeserializeObject<Person>(queueItem);

    var tableOperation = TableOperation.InsertOrReplace(data);
    cloudTable.ExecuteAsync(tableOperation);

    log.LogInformation($"EditPersonQueue trigger function finished.");
}

Note que novamente não há muita mudança em relação nossa Queue Trigger criada no post anterior.

Com isso vamos executar nosso Function App novamente através do comando:

func start

E então abra novamente o Microsoft Azure Storage Explorer e note que a mensagem sumiu da fila EditPerson, e agora o registro encontra-se editado na nossa tabela Person.

Agora que a edição esta finalizada e funcionando perfeitamente, vamos para o próximo passo que é o de excluir o nosso registro, e para isso vamos novamente criar mais uma função através do comando abaixo:

func new

Novamente, escolha o tipo HttpTrigger e então nós daremos a ela o nome de DeletePerson.

Abra o arquivo criado (DeletePerson.cs) e vamos altera-lo com o código abaixo:

public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "delete", Route = null)]HttpRequest req,
    [Queue("DeletePerson", Connection = "AzureWebJobsStorage")]IAsyncCollector<string> queueItem,
    ILogger log)
{
    log.LogInformation("DeletPerson function started a request.");

    await queueItem.AddAsync(
        JsonConvert.SerializeObject(
            new Person
            {
                PartitionKey = "Person",
                RowKey = req.Query["id"]
            }
        )
    );

    log.LogInformation("DeletePerson function finished a request.");

    return new OkObjectResult($"Obrigado! Seu registro já esta sendo processado.");
}

Observe que agora nossa HttpTrigger, ao contrario das criadas anteriormente, espera como body apenas o Partition Key (coloquei ele já direto no código, mas você pode passa-lo como parâmetro caso queira) e o RowKey que será passado via QueryString sob o parâmetro de nome id.

Vamos agora executar nosso Function App utilizando o comando abaixo:

func start

Note que agora nós temos três endpointsDeletePerson: [DELETE] http://localhost:7071/api/DeletePerson, CreatePerson: [POST] http://localhost:7071/api/CreatePerson e EditPerson: [PUT] http://localhost:7071/api/EditPerson e com isso já é possível testar nossa função de exclusão (para isso recomendo utilizar um Client REST da sua preferência), porém dessa vez conforme precisaremos apenas do RowKey e com isso sua QueryString deve ficar muito parecido com que se encontra logo abaixo:

http://localhost:7071/api/DeletePerson?id=<GUID_DO_REGISTRO>

Feito isso abra a ferramenta Microsoft Azure Storage Explorer e localize dentro de Storage Account o serviços de Queues e abrindo-o note que foi criada uma Queue chamada DeletePerson. Abra a mesma e note que o body que passamos para o nosso endpoint estará armazenado na fila.

Agora nosso próximo passo, assim como na parte 1 do nosso post, será o de desenfileirar a mensagem e salva-la no nosso banco de dados e para isso vamos criar nossa próxima Azure Function, e para isso, novamente vamos utilizar o comando abaixo:

func new

Dessa vez escolheremos o tipo QueueTrigger e colocaremos o nome dela de DeletePersonQueue e em seguida, vamos altera-la com o código abaixo:

public static void Run(
    [QueueTrigger("DeletePerson", Connection = "AzureWebJobsStorage")]string queueItem,
    [Table("Person")]CloudTable cloudTable,
    ILogger log)
{
    log.LogInformation($"DeletePersonQueue trigger function started.");

    var data = JsonConvert.DeserializeObject<Person>(queueItem);

    var person = new DynamicTableEntity(data?.PartitionKey, data?.RowKey);
    person.ETag = "*";

    var tableOperation = TableOperation.Delete(person);
    cloudTable.ExecuteAsync(tableOperation);

    log.LogInformation($"DeletePersonQueue trigger function finished.");
}

Note que dessa vez houve uma certa mudança em relação nossa Queue Trigger criada no post anterior.

Aqui nos primeiros temos que recuperar o registro da nossa Table Person, baseando-se na PartitionKey e no RowKey antes de mais nada e na sequência setar a propriedade ETag. Falando rapidamente sobre essa propriedade, ela consegue atuar no caso de concorrência de alterações de um registro, como aqui eu quero excluir o mesmo eu estou setando com o * e isso significa que eu estou dizendo para o Tables não se preocupar em monitorar essa exclusão.

Com isso vamos executar nosso Function App novamente através do comando:

func start

E então abra novamente o Microsoft Azure Storage Explorer e note que a mensagem sumiu da fila DeletePerson, e agora o registro também não existe mais na nossa tabela Person.

Bom pessoal, e com isso finalizamos a segunda parte do nosso CRUD Serverless em que editamos e excluímos o nosso registro. No nosso próximo post vamos trabalhar na exibição dos registros já cadastrados e na publicação do nosso Function App no Azure.

E para finalizar deixo aqui um convite.

Que tal aprender mais sobre Docker, Kubernetes e a implementação de soluções baseadas em containers utilizando o Microsoft Azure, em um workshop que acontecerá durante um sábado todo (dia 04/04) em São Paulo Capital e implementando um case na prática?

Acesse então o link a seguir para efetuar sua inscrição (já incluso camiseta, emissão de certificado e almoço para os participantes) com o desconto: http://bit.ly/anp-docker-blog-ericson

Até mais pessoal.

Abraços e Obrigado!

Criando um CRUD Serverless com Azure Functions – Parte 1

Olá pessoal tudo bem?

Iniciaremos através deste post uma série sobre como montar um CRUD completo utilizando a última versão (3) do Azure Functions.

Importante: Para concluirmos o CRUD de maneira satisfatória, vamos precisar instalar as seguintes ferramentas:

No Visual Studio Code recomendo fortemente instalar a extensão do Azure Functions, pois ela vai nos ajudar bastante.

Assim que todas as instalações forem concluídas, vamos executar o Azure Storage Emulator (caso o mesmo já não esteja em execução), e então na sequência, vamos criar nosso projeto no Visual Studio Code, abrindo seu Terminal (Ctrl + ‘) e em seguida digitando o seguinte comando:

func init

Feito vão aparecer 4 opções de seleção, nesse caso, selecione a primeira opção “dotnet”, e então, espere que termine a criação do nosso Function App.

Nosso próximo passo será o de criar nossa primeira Azure Function, e para isso digite o comando abaixo:

func new

Feito isso apareceram diversas opções de Azure Functions, cada uma para um propósito diferente, e no nosso caso escolheremos o tipo HttpTrigger e daremos a ela o nome de CreatePerson.

Abra o arquivo criado (CreatePerson.cs) e observe que ele já veio com um código padrão. Esse código nada mais faz, do que esperar um parâmetro chamado name e no fim exibirá uma mensagem de boas vindas.

Note que a função possui um cabeçalho, e ele e de extrema importância, pois é através dele que “injetaremos” os serviços que desejamos consumir na nossa Function.

Dica 1: Por boas práticas inclusive recomendadas no livro Clean Code do Robert C. Martin (Cap. 3 – Functions), uma Função deve ser extremamente pequena e no caso das Azure Functions isso não é excessão, lembre sempre que se uma Função tiver mais de um propósito, faça uma refatoração da mesma.

Dica 2: Quando suas Azure Functions estiverem publicadas no Azure, caso o plano escolhido seja o Consumption (mais barato), uma função tem tempo limite de 10 minutos para executar, logo já é mais que um bom motivo para suas funções serem “pequenas”.

Voltando para a nossa função CreatePreson vamos substitui o código padrão pelo código abaixo:

public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequest req, 
    [Queue("CreatePerson", Connection = "AzureWebJobsStorage")]IAsyncCollector<string> queueItem, 
    ILogger log)
{
    log.LogInformation("CreatePerson function started a request.");

    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    await queueItem.AddAsync(requestBody);

    log.LogInformation("CreatePerson function finished a request.");

    return new OkObjectResult($"Obrigado! Seu registro já esta sendo processado.");
}

Observe que o Visual Studio Code vai indicar um erro, na palavra Queue e na propriedade Connection. Isso deve ao fato de que ele não encontrou as mesmas e portanto precisamos importar o SDK do Azure Storage na nossa aplicação, e para fazer isso, abra o arquivo .csproj do seu projeto e logo abaixo da linha em que ele adiciona a referência do SDK das Azure Functions, adicione a seguinte referência:

<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="3.0.10" />

Com isso ele vai pedir para fazer o Restore do seu projeto e em seguida os erros deixarão de existir.

Comentando sobre o código da nossa Function, nós basicamente “injetamos” a possibilidade de lidar com as Queues do Azure Storage, e então através da nossa HttpTrigger receberemos o conteúdo contido no body da nossa requisição e então, enfileiraremos esse conteúdo no serviço de mensageria do Azure Storage. Note também, que na propriedade Connection eu passo um valor chamado “AzureWebJobsStorage”, nesse caso, vamos abrir o arquivo local.settings.json e localizar a propriedade “AzureWebJobsStorage” e então, substituir o conteúdo dela para a String de Conexão do nosso Azure Storage Emulator. Caso você possua Mac ou Linux você pode indicar nesse local a String de Conexão do seu Azure Storage no Azure.

Como desejamos fazer um CRUD completo, vou utilizar a classe abaixo (Person.cs) como referência para o nosso cadastro.

using Microsoft.WindowsAzure.Storage.Table;

namespace AzureFunctions
{
    public class Person : TableEntity
    {
        public string Name { get; set; }
        public string Email { get; set; }

        public Person() { }
    }
}

Observe que na classe Person eu já herdo a classe TableEntity para já facilitar a inserção dos nosso dados em um outro serviço do Azure Storage chamado Azure Tables.

Feito isso, vamos testar nosso Function App, e para isso digite o seguinte comando:

func start

Note que ele criou o seguinte endpoint para nós: CreatePerson: [POST] http://localhost:7071/api/CreatePerson e com isso já é possível testar nossa função (para isso recomendo utilizar um Client REST da sua preferência) passando o seguinte body:

{
	"name": "Blog do Ericson",
	"email": "ericson@email.com"
}

Feito isso abra a ferramenta Microsoft Azure Storage Explorer (instalada previamente no inicio do post) e localize dentro de Storage Account o serviços de Queues e abrindo-o note que foi criada uma Queue chamada CreatePerson. Abra a mesma e note que o body que passamos para o nosso endpoint estará armazenado na fila.

Agora nosso próximo passo será desenfileirar a mensagem e salva-la em um banco de dados, e seguindo nosso exemplo esse banco de dados sera o serviço de Tables (banco de dados NoSQL de chave-valor) do Azure Storage.

Vamos então criar uma nova Azure Function, digitando o novamente o comando abaixo:

func new

Feito isso, novamente aparecerão diversas opções de Azure Functions, e dessa vez escolheremos o tipo QueueTrigger e nós daremos a ela o nome de CreatePersonQueue. Após a criação da mesma substitua o código gerado pelo template pelo código abaixo:

public static void Run(
    [QueueTrigger("CreatePerson", Connection = "AzureWebJobsStorage")]string queueItem, 
    [Table("Person")]CloudTable cloudTable, 
    ILogger log)
{
    log.LogInformation($"CreatePersonQueue trigger function started.");

    var data = JsonConvert.DeserializeObject<Person>(queueItem);
    data.PartitionKey = "Person";
    data.RowKey = Guid.NewGuid().ToString();

    var tableOperation = TableOperation.Insert(data);
    cloudTable.ExecuteAsync(tableOperation);

    log.LogInformation($"CreatePersonQueue trigger function finished.");
}

No código acima, vamos deserializar a mensagem para o tipo Person e em seguida precisamos criar duas propriedades obrigatórias para que se possa utilizar o serviço de Tables do Azure Storage. Explicando de maneira bem resumida, a RowKey teria uma função similar ao de um ID de um banco de dados relacional e a PartitionKey teria a função de ajustar o particionamento automático do serviço de Tables do Azure Storage.

Com isso vamos executar nosso Function App novamente através do comando:

func start

E então abra novamente o Microsoft Azure Storage Explorer e note que a mensagem sumiu da fila, e agora a mesma encontra-se salva na tabela Person (criada automaticamente) dentro do serviço de Tables.

Bom pessoal, e com isso finalizamos a primeira parte do nosso CRUD Serverless em que criamos nosso registro. No nosso próximo post vamos trabalhar na edição e exclusão dele.

E para finalizar deixo aqui um convite.

Que tal aprender mais sobre Docker, Kubernetes e a implementação de soluções baseadas em containers utilizando o Microsoft Azure, em um workshop que acontecerá durante um sábado todo (dia 04/04) em São Paulo Capital e implementando um case na prática?

Acesse então o link a seguir para efetuar sua inscrição (já incluso camiseta, emissão de certificado e almoço para os participantes) com o desconto: http://bit.ly/anp-docker-blog-ericson

Até mais pessoal.

Abraços e Obrigado!

Exibindo o último commit do git na sua aplicação ASP.NET Core

Nesse post vou demonstrar como exibir o último commit do Git na sua aplicação ASP.NET Core.

Primeiramente clique com o botão direito sobre sobre sua aplicação e clique em Properties, em seguida clique em Build Events e na sequência clique em Edit Pre-build e digite a seguinte linha de código que se encontra no bloco abaixo:

git log -1 > "$(ProjectDir)versao.txt"

O comando acima pega o log do último commit e cria um arquivo chamado versao.txt no diretório base do seu projeto. Observe que utilizei a macro ProjectDir para automaticamente criar meu arquivo com o último log no diretório base do projeto. Caso queira saber mais sobre macros, na tela de Edit Pre-build, clique no botão Macros >> haverão muitas opções que poderão lhe auxiliar, caso queira fazer algo mais elaborado.

Nosso próximo passo será colocar nosso arquivo versao.txt “embutido” na nossa aplicação e para isso, clique sobre o arquivo versao.txt com o botão direito e em seguida clique em Properties. Na opção BuildAction selecione Embedded resource e na opção Copy to Output Directory selecione Copy always.

Por fim, agora vamos ler nosso arquivo versao.txt para exibir dentro da sua aplicação, utilizando o código abaixo:

string gitVersion = string.Empty;
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("SuaAplicacao.versao.txt"))
using (var reader = new StreamReader(stream))
{
    gitVersion = reader.ReadToEnd();
}

Note que a variável gitVersion receberá o conteúdo do último commit.

Espero que tenham gostado do artigo e até a próximo!

Obrigado!

AZ-900 – Azure Fundamentals

Olá pessoal, tudo bem?

Ontem (08/01), tirei a certificação AZ-900 – Azure Fundamentals, e vou contar para vocês aqui como foi todo o processo de estudo até a prova.

O que cai na prova?

Abaixo, vou fazer um resumos dos principais assuntos que são cobrados na prova, lembrando que para essa prova você não precisa saber implementa-los de fato, apenas saber como eles funcionam e suas diferenças.

A prova AZ-900 é divida em quatro tópicos de estudo, sendo eles:

  1. Conceitos de Nuvem
    • Entender a diferença entre IaaS, PaaS e SaaS.
    • Entender a diferença entre nuvem, privada, pública e híbrida.
    • Entender a diferença entre CapEx e OpEx.
    • Entender a diferença entre Elasticidade, Escalabilidade, Disponibilidade, Disaster Recovery e etc.
  2. Serviços do Azure
    • Entender a arquitetura do Azure (Regiões, Zonas de disponibilidade e etc).
    • Entender os principais serviços do Azure (Computação, Storage, Dados, Rede e etc).
    • Entender como funciona o Portal do Azure, Azure Cli, Powershell e Cloud Shell.
    • Entender a utilidade do Azure Advisor.
  3. Segurança, Privacidade e Conformidade
    • Entender a diferença entre Network Security Group (NSG), Firewall, Proteção DDoS.
    • Entender como funciona o Azure Active Directory.
    • Entender para que serve o Azure Key-Vault
    • Entender as metodologias de segurança do Azure (Policies, Locks e etc).
    • Entender as opções de monitoramento e Log (Azure Monitor, Log Analytics e etc).
    • Entender as diferentes normas de conformidade do Azure variando de acordo com cada país.
  4. Precificação e Suporte
    • Entender a diferença entre uma assinatura “Free” e em uma assinatura “normal”.
    • Entender para que serve uma Subscription.
    • Entender a diferença entre os planos de suporte do Azure (Basic, Standart, Professional e etc).
    • Entender o SLA do Azure.

Como estudar para a prova?

Primeiramente sugiro fortemente que caso você já não tenha, crie uma conta no Azure, pois ela será fundamental para o aprendizado.

Feito isso, existe um guia de estudo que a própria Microsoft disponibilizou, contendo os principais tópicos abordados no exame. Caso você já tenha familiaridade com os conceitos de Cloud Computing e com o Azure, esse guia deve ser suficiente para que você passe na prova, entretanto se você ainda não é familiarizado com esse conceitos sugiro complementar seus estudos com as seguintes opções:

Como comprar e aonde fazer a prova?

A prova AZ-900 é um pouco mais barata que as demais provas do Azure, custando hoje $60. Essa prova pode ser adquirida através do site da Microsoft Learning.

Quando estiver comprando você terá a opção de escolher entre fazer a prova em um Testing Center ou na sua casa/escritório, eu escolhi fazer na minha casa mesmo e a experiência foi ótima. Claro que existem regras para se fazer o exame na sua casa e elas devem ser seguidas a risca do contrário você perde o exame. Nesse vídeo feito pela própria Microsoft é retratado de maneira bem fiel como funciona todo o processo.

Últimas dicas

Conforme mencionei no inicio do texto, você não vai implementar nada no Azure, a prova é 100% conceitual, logo a palavra-chave aqui para que você tenha sucesso no dia do exame é: entender as diferenças entre os serviços, entre os modelos e etc. Saber qual serviço é mais adequado para cada situação.

Boa sorte!

Criando um servidor de identidade utilizando o Identity Server 4 e o ASP.NET Core 2 – Parte 2

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:

Identity Server 4
Figura 1

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:

Identity Server 4
Figura 2

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:

Identity Server 4
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:

Identity Server 4
Figura 4

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:

AllowedScopes = {
    IdentityServerConstants.StandardScopes.OpenId,
    IdentityServerConstants.StandardScopes.Profile,
    IdentityServerConstants.StandardScopes.Email,
    "custom.profile"
} 

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:

Figura 5

Bom, pessoal, era isso que eu queria mostrar. Para quem quiser o código completo ele está no meu GitHub.

Muito obrigado e até a próxima!