Web API

Documentando uma ASP.NET Core Web API com o Swagger

Com cada vez mais aplicações multiplataformas, é comum definir uma API REST que será consumida pelas várias versões da aplicação. Mas com vários times tendo acesso a API, não basta mais apenas desenvolvê-la e esperar que todos consigam utilizá-la institivamente.

Na época do web service, existia o WSDL que funcionava como uma documentação, já que facilitava a criação dos clientes que iria consumí-lo. Com as APIs REST não temos esta facilidade, assim, torna-se imprescindível que ela seja bem documentada.

Mas como realizar esta documentação? Existem algumas ferramentas que podem nos auxiliar neste processo, como: API Blueprint, RAML, Swagger, entre outras.

Como o nome deste artigo sugere, aqui abordaremos o Swagger.

Swagger

O Swagger é uma aplicação open source que auxilia os desenvolvedores a definir, criar, documentar e consumir APIs REST. Sendo uma das ferramentas mais utilizadas para esta função, a empresa por trás dela (a SmartBear Software), decidiu criar o Open API Iniciative e renomearam as especificações do Swagger para OpenAPI Specification.

Ela visa padronizar as APIs REST, desta forma descreve os recursos que uma API deve possuir, como endpoints, dados recebidos, dados retornados, códigos HTTP, métodos de autenticação, entre outros.

Para facilitar o processo de especificação/documentação de uma API, o Swagger fornece algumas ferramentas, como:

  • Swagger Editor: Editor que permite especificar uma API. Há uma versão online grátis e aplicativos para desktop;
  • Swagger UI: Interface interativa para a documentação da API;
  • Swagger Codegen: Gera templates de código (mais de 20 linguagens estão disponíveis) de acordo com uma especificação de API.

Neste artigo vamos focar na documentação de uma API existente, criada em ASP.NET Core Web API.

Especificação do Swagger

O ponto mais importante do Swagger é a sua especificação, que era chamada de Swagger specification e agora é OpenAPI Specification. Esta especificação trata-se de um documento, JSON ou YAML, que define a estrutura da API. Indicando os endpoints, formatos de entrada, de saída, exemplos de requisições, forma de autenticação, etc.

Abaixo você pode ver um exemplo desta especificação definida em JSON:

{
   "swagger": "2.0",
   "info": {
       "version": "v1",
       "title": "API V1"
   },
   "basePath": "/",
   "paths": {
       "/api/Example": {
           "get": {
               "tags": [
                   "Example"
               ],
               "operationId": "ApiExampleGet",
               "consumes": [],
               "produces": [
                   "text/plain",
                   "application/json",
                   "text/json"
               ],
               "responses": {
                   "200": {
                       "description": "Success",
                       "schema": {
                           "type": "array",
                           "items": {
                               "$ref": "#/definitions/Item"
                           }
                       }
                   }
                }
           },
           "post": {
               ...
           }
       },
       "/api/Example/{id}": {
           "get": {
               ...
           },
           "put": {
               ...
           },
           "delete": {
               ...
   },
   "definitions": {
       "Item": {
           "type": "object",
            "properties": {
                "id": {
                    "format": "int64",
                    "type": "integer"
                },
                "name": {
                    "type": "string"
                },
                "isValid": {
                    "default": false,
                    "type": "boolean"
                }
            }
       }
   },
   "securityDefinitions": {}
}

É possível definir esta especificação na mão, olhando as opções na documentação, através do Swagger Editor ou utilizando outra ferramenta para gerá-la.

No ASP.NET Core podemos utilizar duas bibliotecas para gerar este documento:

Por ser mais completa, faremos uso da Swashbuckle.

Adicionando o Swashbuckle na aplicação

Para este exemplo, estou utilizando uma aplicação ASP.NET Core Web API, baseada no exemplo fornecido pela documentação da Microsoft, que pode ser vista aqui. Você pode ver a aplicação que criei no meu Github.

Com a aplicação Web API criada, inicialmente é necessário adicionar o pacote do Swashbuckle na aplicação. Ele possui três componentes:

Mas é necessário instalar apenas o pacote Swashbuckle.AspNetCore. Por se tratar de uma aplicação .NET Core, este pacote pode ser adicionado com o comando abaixo:

dotnet add package Swashbuckle.AspNetCore

Após isso, para que o arquivo de especificação da API do Swagger seja criado, é necessário adicionar o gerador dele nos serviços da aplicação, no método ConfigureServices da classe Startup:

public void ConfigureServices(IServiceCollection services)
{
    //... código omitido

    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new Info { Title = "TodoAPI", Version = "v1" });
    });
}

Para a classe Info, defina o using do namespace abaixo:

using Swashbuckle.AspNetCore.Swagger;

Agora, no método Configure é necessário ativar o Swagger e indicar o local onde o seu arquivo de especificação será criado:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    //Ativa o Swagger
    app.UseSwagger();

    // Ativa o Swagger UI
    app.UseSwaggerUI(opt =>
    {
        opt.SwaggerEndpoint("/swagger/v1/swagger.json", "TodoAPI V1");
    });

    //... código omitido
}

Com isso, através de reflection a biblioteca consegue especificar os endpoints da aplicação.

Caso seja executada, podemos acessar o Swagger UI da aplicação no caminho /swagger:

Caso queira que o Swagger UI seja acessado a partir da raiz da aplicação, na ativação dela, no método Configure, atribua vazio para a propriedade RoutePrefix:

app.UseSwaggerUI(c =>
{
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "TodoAPI V1");
    c.RoutePrefix = string.Empty;
});

Customizando o Swagger

Como o Swashbuckle trabalha com reflection, podemos fazê-lo gerar mais informações das nossas APIs com base em configurações definidas nela.

Comentários de documentação

Sabemos que no C# podemos adicionar comentários de documentação nos nossos códigos, como:

/// <summary>
/// Lista os itens da To-do list.
/// </summary>
/// <returns>Os itens da To-do list</returns>
/// <response code="200">Returna os itens da To-do list cadastrados</response>
[HttpGet]
public ActionResult<List<Item>> Get()
{
    return _repository.GetAll();
}

É possível fazer o Swashbuckle ler esses comentários e assim tornar a especificação da API mais detalhada.

Para isso, na definição de geração do arquivo de especificação, no método ConfigureServices deve ser adicionado a informação abaixo:

services.AddSwaggerGen(opt =>
{
    opt.SwaggerDoc("v1", new Info { Title = "TodoAPI", Version = "v1" });

    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
    opt.IncludeXmlComments(xmlPath);
});

Note que estamos indicando que ele deve ler os comentários do código a partir de um arquivo XML. Para que eles sejam gerados neste arquivo indicado, no arquivo de configuração do projeto (o .csproj), adicione o trecho abaixo:

<PropertyGroup>
  <GenerateDocumentationFile>true</GenerateDocumentationFile>
  <NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>

O <NoWarn> é definido para que a IDE não exiba um alerta sobre os códigos que não possuírem comentários de documentação.

Agora ao acessar o Swagger API, o que foi definido em <summary> será mostrado:

Também podemos o <remarks> para indicar um exemplo de requisição:

// POST api/todo
/// <summary>
/// Cria um item na To-do list.
/// </summary>
/// <remarks>
/// Exemplo:
///
///     POST /Todo
///     {
///        "id": 1,
///        "name": "Item1",
///        "iscomplete": true
///     }
///
/// </remarks>
/// <param name="value"></param>
/// <returns>Um novo item criado</returns>
/// <response code="201">Retorna o novo item criado</response>
/// <response code="400">Se o item não for criado</response>        
[HttpPost]
public ActionResult<Item> Post([FromBody] Item value)
{
    Console.WriteLine(value?.Name);

    var item = _repository.Save(value);
    if(item != null)
        return item;

    return BadRequest();
}

O resultado será:

Com as anotações ProducesResponseType e Produces podemos indicar os códigos HTTP e o tipo de dado que os endpoints irão retornar:

[Authorize]
[Produces("application/json")]
[Route("api/[controller]")]
[ApiController]
public class TodoController : ControllerBase
{
    //..código omitido

    // GET api/todo
    /// <summary>
    /// Lista os itens da To-do list.
    /// </summary>
    /// <returns>Os itens da To-do list</returns>
    /// <response code="200">Returna os itens da To-do list cadastrados</response>
    [HttpGet]
    [ProducesResponseType(200)]
    public ActionResult<List<Item>> Get()
    {
        return _repository.GetAll();
    }

    //... código omitido
}

Especificando a autenticação

Uma das principais vantagens de adicionar o Swagger na aplicação, é poder testar os endpoints pela Swagger UI. Quando a aplicação define autenticação, é necessário indicar isso para o Swashbuckle, para que ele também gere uma especificação desde detalhe.

Com isso, será possível testar os endpoints pela Swagger UI utilizando a autenticação definida.

Para fazer isso, devemos adicionar a informação abaixo, na definição de geração do arquivo de especificação, no método ConfigureServices:

services.AddSwaggerGen(opt =>
{
    opt.SwaggerDoc("v1", new Info { Title = "TodoAPI", Version = "v1" });

    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
    opt.IncludeXmlComments(xmlPath);

    var security = new Dictionary<string, IEnumerable<string>>
    {
        {"Bearer", new string[] { }},
    };

    opt.AddSecurityDefinition(
        "Bearer",
        new ApiKeyScheme
        {
            In = "header",
            Description = "Copie 'Bearer ' + token'",
            Name = "Authorization",
            Type = "apiKey"
        });

    opt.AddSecurityRequirement(security);
});

Acima estamos definindo que a aplicação faz uso de autenticação do tipo JWT Token (“Bearer Token”). Na documentação do Swagger, você pode ver os outros tipos de autenticação suportados.

Ao acessar o Swagger UI, ele irá mostrar o botão “Autorize”:

Exibe um botão com o texto "Autorize"

Ao clicar nele, é possível informar o tipo de autenticação especificado:

Com isso, mesmo que necessite de autenticação, será possível realizar as requisições dos endpoints da aplicação pelo Swagger UI.

Descrição

Por fim, é possível descrever um pouco da aplicação através das propriedades da classe Info da especificação do Swagger:

services.AddSwaggerGen(opt =>
{
    opt.SwaggerDoc("v1", new Info
    {
        Version = "v1",
        Title = "Todo API",
        Description = "Um exemplo de aplicação ASP.NET Core Web API",
        TermsOfService = "Não aplicável",
        Contact = new Contact
        {
            Name = "Wladimilson",
            Email = "contato@treinaweb.com.br",
            Url = "https://treinaweb.com.br"
        },
        License = new License
        {
            Name = "CC BY",
            Url = "https://creativecommons.org/licenses/by/4.0"
        }
    });

    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
    opt.IncludeXmlComments(xmlPath);

    var security = new Dictionary<string, IEnumerable<string>>
    {
        {"Bearer", new string[] { }},
    };

    opt.AddSecurityDefinition(
        "Bearer",
        new ApiKeyScheme
        {
            In = "header",
            Description = "Copie 'Bearer ' + token'",
            Name = "Authorization",
            Type = "apiKey"
        });

    opt.AddSecurityRequirement(security);
});

O resultado será:

Swagger UI mostrando a descrição da API

Conclusão

O Swagger é uma ótima forma de documentar APIs REST e com a biblioteca Swashbuckle, podemos gerar o arquivo de especificação do Swagger com facilidade. Assim, não há mais desculpa para se definir documentações interativas e detalhadas para as suas APIs REST.

Gerando sons com a Web Audio API do JavaScript

Olá, Web Developers!

Sabiam que podemos gerar sons com JavaScript? Isso graças à Web Audio API, que nos fornece poderosas funcionalidades para se trabalhar com áudio. Podemos fazer coisas como adicionar efeitos a um arquivo de áudio ou criar músicas a partir do JavaScript.

Começando com um beep

Para começar, vamos gerar um simples beep.

let context = new AudioContext(),
   oscillator = context.createOscillator();

oscillator.type = 'sine';
oscillator.connect(context.destination);
oscillator.start();

Primeiro nós instanciamos o AudioContext, que é o principal objeto para podermos gerar um som. A partir dele nós criamos um oscilador, que é responsável por emitir um sinal contínuo, que será o nosso som.

Precisamos indicar o tipo de onde que nosso oscilador irá gerar. Indicamos que queremos uma onda senoidal (sine).

Depois conectamos o nosso oscilador ao context.destination, que representa o destino final do áudio. Se você está no computador, provavelmente isso indica as suas caixas de som.
Por fim, executamos a função start() para iniciar o som.

Para parar o som, basta executar oscillator.stop().

Ao parar o som você pode notar um pequeno clique. Isso acontece basicamente quando paramos o som em um lugar qualquer que não seja o ponto zero da onda, como mostrado na imagem abaixo.

Onda Senoidal

Encerrando o som

Um modo de acabar com esse som de clique ao pararmos o som (e que pode ser bem desconfortável) é alterar a nossa onda, como se estivéssemos diminuindo ela.

Para isso, usamos a função exponentialRampToValueAtTime(), que vai alterando de forma exponencial o valor do nosso som.

Vamos então fazer uma pequena alteração em nosso código:

let context = new AudioContext(),
   oscillator = context.createOscillator(),
   contextGain = context.createGain();

oscillator.connect(contextGain);
contextGain.connect(context.destination);
oscillator.start(0);

Agora criamos um ganho, um objeto que nos permitirá trabalhar com a função exponentialRampToValueAtTime(). Ligamos ele ao oscilador e depois ligamos ao destino de saída do som.

Para parar o som, ao invés de simplesmente executarmos a função stop(), vamos executar o seguinte código:

contextGain.gain.exponentialRampToValueAtTime(
    0.00001, context.currentTime + 0.04
    )

O que fizemos aqui foi alterar o valor do ganho, que basicamente seria como se estivéssemos abaixando o volume aos poucos.

Veja que o primeiro parâmetro que passamos é bem próximo de zero. Isso porque essa função não pode receber um valor zerado ou negativo.

O segundo parâmetro é o tempo em que queremos que a alteração seja feita.

Abaixo você pode conferir que não importa mais em que momento pare o som, ele não terá mais aquele clique.

Efeitos ao parar o som

Quando paramos o som, passamos um tempo bem pequeno para que a alteração no ganho fosse realizada (0.4).

Dependendo do tempo que passarmos, podemos ter efeitos interessantes.

Isso acaba nos dando sensações diferentes. E essa sensação fica ainda mais evidente quando iniciamos e já encerramos o som logo em seguida.

Sons bens mais agradáveis do que aquele som contínuo, não é mesmo?

Diferentes ondas, diferentes osciladores

O que criamos até agora foi um som a partir de um oscilador usando uma onda senoidal. Há outros tipos de ondas, como podemos ver na seguinte imagem:

Para alterar o tipo de onda, basta passarmos um dos seguintes códigos:

oscillator.type = 'sine';
oscillator.type = 'square';
oscillator.type = 'triangle';
oscillator.type = 'sawtooth';

Tocando notas

Até o momento nós só tocamos um simples beep. Mas se quisermos tocar algo, precisamos tocar notas musicais diferentes.

Cada nota é um som emitido a uma determinada frequência. Abaixo temos uma tabela que indica cada nota e sua respectiva frequência:

Para alterar a frequência, basta indicar um valor em oscillator.frequency.value. Para tocar um “Dó” (C4):

const C4 = 261.6,
    D4 = 293.7,
  E4 = 329.6,
  F4 = 349.2,
  G4 = 392.0,
  A4 = 440.0,
  B4 = 493.9;

oscillator.frequency.value = C4;

No exemplo, passei a frequência para uma constante apenas para o nosso código ficar mais legível. É bem mais simples e legível deixar o nome das notas do que deixar o número da frequência.

Evoluindo

Caso queira se aprofundar, procure saber mais sobre bibliotecas e frameworks como o Tone.js, uma ferramenta completa com várias funcionalidades para se trabalhar com música interativa no navegador.

Criando uma aplicação distribuída com ASP.NET Core e o Microsoft Orleans

Este é mais um artigo de uma série sobre o Microsoft Orleans. Caso não tenha visto o primeiro, recomendo que o leia: Conhecendo o Microsoft Orleans.

Como dito no artigo passado, o Orleans está na segunda versão e uma das novidades dela é o suporte ao .NET Standard 2.0, desta forma, como o título indica, a aplicação demonstrada aqui utilizará este framework.

Estrutura da solução

Antes de colocarmos a mão na massa, é importante entender como funciona a solução do Orleans. Esta biblioteca recomenda que um projeto que a implemente seja estruturado com no mínimo quatro projetos:

  • OrleansHost: Este projeto irá criar um executável que iniciará os silos, os hosts do Orleans que conterá as instâncias dos grãos. É possível iniciar vários silos, que irão trabalhar em conjunto e compartilhar a carga.

  • GrainsInterfaces: Este projeto contém as interfaces que definem todos os grãos contido nos silos.

  • Grains: Este projeto contém a implementação de todos os grãos definidos em GrainsInterfaces. Por isso, ele é implementado em conjunto com o OrleansHost.

  • Inteface: Este projeto pode ser qualquer projeto de interface, mas geralmente trata-se de API. Ele irá se conectar ao OrleansHost, por TCP, para ter acesso aos grãos.

Durante o desenvolvimento, o projeto interface e o OrleansHost podem ser executados em conjunto na mesma máquina. Mas em produção, geralmente eles são executados separadamente. O OrleansHost é implementado em um cluster e a interface em um servidor web.

Neste artigo, a solução será feita em uma máquina Mac OSX, utilizando a versão 2.1.4, em conjunto com o Visual Studio Code, pois amo este ambiente. Mas o Orleans fornece um plugin de templates para o Visual Studio, que facilita a criação de projetos com esta biblioteca. Desta forma, caso esteja utilizando esta IDE recomendo que instale este plugin.

Criando o projeto GrainsInterfaces

Como o projeto Grains necessita do GrainsInterfaces e o OrleansHost fará uso do Grains, o primeiro projeto que precisa ser criado é o GrainsInterfaces.

Caso esteja utilizando o Visual Studio com o plugin do Orleans instalado, crie um projeto com base no template “Orleans Grain Interface Collection. No meu ambiente, incialmente irei criar uma pasta chamada OrleansDotNet, e dentro dela uma solução:

dotnet new sln -n OrleansDotNet

Em seguida, um projeto Class Library:

dotnet new classlib -n GraosInterfaces

Neste projeto adicione a biblioteca Microsoft.Orleans.OrleansCodeGenerator.Build:

dotnet add package Microsoft.Orleans.OrleansCodeGenerator.Build

E adicione nele a interface abaixo:

using System;
using Orleans;
using System.Threading.Tasks;

namespace OrleansDotNet.GraosInterfaces
{
    public interface IGraoContador: IGrainWithStringKey
    {
        Task Incremento(int incremento);
        Task<int> GetContador();
    }
}

As interfaces grãos devem definir a implementação de uma das interfaces “GrainWith*” do Orleans e definir métodos que retornem uma Task, para métodos void, ou Task, caso o método retorne algum valor.

A interface implementada acima, IGrainWithStringKey, é utilizada para indicar que o código de identificação do grão será definido com uma string. Infelizmente a documentação do Orleans ainda não documenta bem todas as interfaces disponíveis, mas em um artigo futuro abordo todas.

Criando o projeto Grains

Com a interface definida podemos implementá-la um um projeto Grains. Caso esteja utilizando o Visual Studio, crie um projeto com o template Orleans Grain Class Collection. No meu ambiente, irei criar um projeto Class Library chamado Graos:

dotnet new classlib -n Graos

E este projeto também precisa da referência abaixo:

dotnet add package Microsoft.Orleans.OrleansCodeGenerator.Build

Em seguida, referencie o projeto de interface:

dotnet add Graos.csproj reference ../GraosInterfaces/GraosInterfaces.csproj

Agora podemos implementar o nosso grão:

using System;
using Orleans;
using System.Threading.Tasks;
using OrleansDotNet.GraosInterfaces;

namespace OrleansDotNet.Graos
{
    public class GraoContador: Grain, IGraoContador
    {
        private int _contador;

        public Task Incremento(int incremento)
        {
            _contador += incremento;
            return Task.CompletedTask;
        }

        public Task<int> GetContador()
        {
            return Task.FromResult(_contador);
        }
    }
}

Esta classe não tem segredo, o importante é implementar a nossa interface grão e herdar a classe Grain. Dentro dela é definido um contador simples.

Criando o projeto OrleansHost

Com o grão definido, podemos definir o nosso silo (host). Caso esteja utilizando o Visual Studio, crie um projeto com base no template “Orleans Dev/Test Host“.

No meu ambiente irei criar uma aplicação console chamada Silo:

dotnet new console -n Silo

Nele adicione adicione uma referência para a biblioteca Microsoft.Orleans.Server:

dotnet add package Microsoft.Orleans.Server

Como iremos registrar o log do silo do console, adicione a referencia abaixo:

dotnet add package Microsoft.Extensions.Logging.Console

Por fim, adicione a referencia do projeto Graos:

dotnet add Silo.csproj reference ../Graos/Graos.csproj

E na classe Program adicione o código abaixo:

using System;
using Orleans;
using Orleans.Runtime.Configuration;
using Orleans.Hosting;
using Orleans.Configuration;
using System.Threading.Tasks;
using OrleansDotNet.Graos;
using Microsoft.Extensions.Logging;
using System.Runtime.Loader;
using System.Threading;
using System.Net;

namespace OrleansDotNet.Silo
{
    class Program
    {
        private static ISiloHost silo;
        private static readonly ManualResetEvent siloStopped = new ManualResetEvent(false);

        static void Main(string[] args)
        {

            silo = new SiloHostBuilder()
                .UseLocalhostClustering()
                .Configure<ClusterOptions>(options =>
                {
                    options.ClusterId = "OrleansDotNet-cluster";
                    options.ServiceId = "OrleansDotNet";
                })
                .Configure<EndpointOptions>(options => options.AdvertisedIPAddress = IPAddress.Loopback)
                .ConfigureApplicationParts(parts => parts.AddApplicationPart(typeof(GraoContador).Assembly).WithReferences())
                .ConfigureLogging(logging => logging.AddConsole())
                .Build();

            Task.Run(StartSilo);

            AssemblyLoadContext.Default.Unloading += context =>
            {
                Task.Run(StopSilo);
                siloStopped.WaitOne();
            };

            siloStopped.WaitOne();

        }

        private static async Task StartSilo()
        {
            await silo.StartAsync();
            Console.WriteLine("Silo iniciado");
        }

        private static async Task StopSilo()
        {
            await silo.StopAsync();
            Console.WriteLine("Silo parado");
            siloStopped.Set();
        }
    }
}

Nesta classe, estamos definindo que o Silo irá utilizar as configurações de um cluster local (UseLocalhostClustering()), também é definido as identificações do cluster, bem com o seu IP (Configure()). Por fim, se define o grão que será adicionado ao silo e onde o seu log deve ser exibido.

Com o silo definido, ele é iniciado. Ele só irá parar em caso de algum erro ou quando o usuário forçar a sua parada.

Criando o projeto Interface

Com o Silo definido podemos criar a nossa aplicação interface. Neste projeto vou criar uma aplicação WebAPI chamada InterfaceAPI:

dotnet new webapi -n InterfaceApi

Aproveite e já vincule os projetos a solução:

dotnet sln OrleansDotNet.sln add **/*.csproj

Na api é necessário adicionar a referência Microsoft.Orleans.Client:

dotnet add package Microsoft.Orleans.Client

E referenciar o projeto GraosInterfaces:

dotnet add InterfaceApi.csproj reference ../GraosInterfaces/GraosInterfaces.csproj

Para trabalhar com “dependency injection“, vamos configurar o cliente do Orleans na classe Startup conforme o código abaixo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Orleans;
using Orleans.Runtime.Configuration;
using Orleans.Hosting;
using System.Net;
using Orleans.Configuration;
using OrleansDotNet.GraosInterfaces;

namespace InterfaceApi
{
    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();

            services.AddSingleton<IClusterClient>(provider =>
            {
                var client = new ClientBuilder()
                            .UseLocalhostClustering()
                            .Configure<ClusterOptions>(options =>
                            {
                                options.ClusterId = "OrleansDotNet-cluster";
                                options.ServiceId = "OrleansDotNet";
                            })
                            .ConfigureApplicationParts(parts => parts.AddApplicationPart(typeof(IGraoContador).Assembly).WithReferences())
                            .ConfigureLogging(logging => logging.AddConsole())
                            .Build();

                StartClientWithRetries(client).Wait();

                return client;
            });
        }

        private static async Task StartClientWithRetries(IClusterClient client)
        {
            for (var i=0; i<5; i++)
            {
                try
                {
                    await client.Connect();
                    return;
                }
                catch(Exception)
                { }
                await Task.Delay(TimeSpan.FromSeconds(5));
            }
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMvc();
            app.UseStaticFiles();
        }
    }
}

Note que a configuração do cliente é parecida com o do host:

var client = new ClientBuilder()
            .UseLocalhostClustering()
            .Configure<ClusterOptions>(options =>
            {
                options.ClusterId = "OrleansDotNet-cluster";
                options.ServiceId = "OrleansDotNet";
            })
            .ConfigureApplicationParts(parts => parts.AddApplicationPart(typeof(IGraoContador).Assembly).WithReferences())
            .ConfigureLogging(logging => logging.AddConsole())
            .Build();

As diferenças é que agora estamos utilizando a classe ClientBuilder e se adiciona a interface IGraoContador (em detrimento a GraoContador definida no host.

Nesta classe também foi definido o método StartClientWithRetries que tenta se conectar ao host 5 vezes antes de desistir. Quando a conexão for obtida, o objeto client é retornado.

Este cliente será obtido no controller, conforme o código abaixo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Orleans;
using OrleansDotNet.GraosInterfaces;

namespace InterfaceApi.Controllers
{
    [Route("api/[controller]")]
    public class ContadorController : Controller
    {
        private IClusterClient client;

        public ContadorController(IClusterClient client){
            this.client = client;
        }

        [HttpGet]
        public async Task<int> Get()
        {
            var contador = client.GetGrain<IGraoContador>("TW-1");

            return await contador.GetContador();
        }


        [HttpPost]
        public async Task Post()
        {
            var contador = client.GetGrain<IGraoContador>("TW-1");
            await contador.Incremento(1);
        }
    }
}

Para obter o grão, utilizamos o método GetGrain. A este método deve ser passado a chave do grão:

var contador = client.GetGrain<IGraoContador>("TW-1");

Como definimos que a chave dele será uma string, acima estou passando uma string arbitrária. A partir da instância obtida os métodos do grão são chamados, como o GetContador():

return await contador.GetContador();

Com isso, nós estamos obtendo informações do nosso grão contido no silo.

Para finalizar, criei dentro da pasta wwwroot um arquivo HTML, onde os métodos da api são chamados:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">


    <title>Exemplo de Orleans, no ASP.NET Core *.*</title>
</head>
<body>
    <div class="container">
        <div class="card">
            <div class="card-body">
                Contador: <spam id="countValor">0</spam>

                <button id="btnIncrement" class="btn-primary">Incrementar</button>
            </div>
        </div>


    </div>


    <!-- JavaScript -->
    <script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>

    <script type="text/javascript">
        $(document).ready(function () {
            $.get("api/contador/", function (value) {
                console.log('GET contador=' + value);

                $('#countValor').html(value);
            });

            $('#btnIncrement').click(function () {
                $.post("api/contador/", function () {
                    $.get("api/contador/", function (value) {
                        console.log('GET contador=' + value);

                        $('#countValor').html(value);
                    });
                });
                return false;
            });
        });
    </script>
</body>
</html>

Pronto, a nossa aplicação está finalizada. Para testá-la, primeiro é necessário iniciar o Silo:

Em seguida a aplicação web:

No navegador teremos o resultado abaixo:

Ao clicar em “Incrementar“, veremos o valor do contador ser incrementado:

Conclusão

Este é um exemplo simples, porém funcional, que nos dá uma noção do poder do Orleans. Em artigos futuros mostrarei mais detalhes dele.

Caso queria executar a aplicação demostrada aqui no curso, você pode vê-la aqui no GitHub.

JUNTE-SE A MAIS DE 150.000 PROGRAMADORES