C#

Compreendendo os middlewares no ASP.NET Core

Desde a sua primeira versão, o ASP.NET faz uso dos middlewares. Eles foram implementados como uma forma de modularizar uma aplicação ASP.NET facilmente.

Em termos práticos, middleware seria um trecho de código que pode ser executado no fluxo de execução da aplicação. No ASP.NET os middleware são organizados em um pipeline e são executados conforme uma solicitação é recebida e uma resposta enviada. A imagem abaixo ilustra este pipeline:

A imagem apresenta uma solicitação sendo processada por três middlewares, sendo que cada um chamado o middleware seguinte. No terceiro middleware, uma resposta é retornada e esta passa pelos middleware chamados anteriormente.

Cada middleware pode executar uma ação no recebimento da solicitação, chamar o próximo middleware, utilizando o método next(), e executar outra ação durante o retorno da resposta. Só não é possível modificar a resposta no seu retorno.

Dependendo da funcionalidade, o middleware pode decidir não chamar o próximo no pipeline, não invocando o método next(). Por exemple, o middleware de arquivos estáticos pode retornar uma solicitação para um arquivo estático e interromper o fluxo restante.

É possível definir middleware de diversas funcionalidades, por exemplo, no trecho de código abaixo, temos oito middlewares:

  1. Exception/error handling
  2. HTTP Strict Transport Security Protocol
  3. HTTPS redirection
  4. Static file server
  5. Cookie policy enforcement
  6. Authentication
  7. Session
  8. MVC
public void Configure(IApplicationBuilder app)
{
    if (env.IsDevelopment())
    {
        // Quando executado em desenvolvimento:
        //   Utiliza Developer Exception Page para reportar erros.
        //   Utiliza Database Error Page para reportar erros do banco.
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        // Quando não estiver em produção:
        //   Habilita o middleware Exception Handler Middleware para pegar os erros.
        //   Utiliza o middleware que habilita o 
        //       HTTP Strict Transport Security Protocol (HSTS)
        app.UseExceptionHandler("/Error");
        app.UseHsts();
    }

    // Utiliza o middleware HTTPS Redirection que redireciona solicitações HTTP para HTTPS.
    app.UseHttpsRedirection();

    // Retorna arquivos estáticos e interrompe o pipeline.
    app.UseStaticFiles();

    // Utiliza o middleware Cookie Policy, que está em conformidade com 
    // as regras do GDPR (General Data Protection Regulation).
    app.UseCookiePolicy();

    // Autentica antes de utilizar os recursos.
    app.UseAuthentication();

    // Se o aplicativo utiliza sessão, chama o middleware Session depois do middleware 
    // Cookie Policy e antes do middleware MVC.
    app.UseSession();

    // Adiciona MVC ao pipeline da solicitação
    app.UseMvc();
}

Como é possível notar, no ASP.NET todos os middlewares são definidos no método Configure no padrão Use* seguindo do nome do middleware. Neste método também é possível adicionar middlewares customizados, utilizando os métodos Use ou Run. A diferença entres eles é que os middlewares definidos com Run são middlewares “finais”, após eles, nenhum outro middleware é chamado.

C# (C Sharp) - Introdução ao ASP.NET Core
Curso de C# (C Sharp) - Introdução ao ASP.NET Core
CONHEÇA O CURSO

Entendendo a ordem do pipeline

Para compreender a ordem de execução dos middlewares no pipeline, vamos definir alguns middlewares simples:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Primeiro middleware (antes)");
        await next();
        await context.Response.WriteAsync("Primeiro middleware (depois)");
    });

    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Segundo middleware (antes)");
        await next();
        await context.Response.WriteAsync("Segundo middleware (depois)");
    });

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Middleware final");
    });
}

Neste exemplo os middlewares estão sendo definidos como métodos anônimos, em um artigo futuro mostrarei as formas de declará-los.

Neste ponto o importante é que sabia que os middlewares definidos com Use recebem dois parâmetros: a instância de um objeto HttpContext e a instância do delegate RequestDelegate, que aponta para o próximo middleware no pipeline.

O resultado do código acima será algo assim:

Primeiro middleware (antes)
Segundo middleware (antes)
Middleware final
Segundo middleware (depois)
Primeiro middleware (depois)

Note que a ordem que esses middlewares estiverem definidos no método Configure irá indicar a ordem que eles serão chamados no pipeline. Ao chegar o último middleware do pipeline, os middlewares anteriores são chamados novamente.

Short-circuiting middleware

Caso algum dos middlewares não invocar o método next(), o pipeline será encerrado antes do seu final:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Primeiro middleware (antes)");
        await next();
        await context.Response.WriteAsync("Primeiro middleware (depois)");
    });

    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Segundo middleware (antes)");
        // await next();
        await context.Response.WriteAsync("Segundo middleware (depois)");
    });

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Middleware final");
    });
}

O resultado será:

Primeiro middleware (antes)
Segundo middleware (antes)
Segundo middleware (depois)
Primeiro middleware (depois)

Um middleware não final, que não invoca o método next, é chamado de short-circuiting middleware.

Dividindo o pipeline

Nos exemplos anteriores, o nosso pipeline possuía apenas um fluxo: o primeiro middleware sempre vem antes do segundo e por fim é chamado o middleware final. Mas não precisa ser sempre desta forma. Dependendo da solicitação pode ser definido um fluxo diferente para o pipeline.

Criando novo fluxo final

Com os métodos Map ou MapWhen é possível definir um novo fluxo final para o pipeline. O método Map permite especificar um middleware que será invocado de acordo com o caminho da solicitação. Já o MapWhen possui mais poder porque o padrão pode ser definido utilizando o objeto HttpContext:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Primeiro middleware (antes)");
        await next();
        await context.Response.WriteAsync("Primeiro middleware (depois)");
    });

    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Segundo middleware (antes)");
        await next();
        await context.Response.WriteAsync("Segundo middleware (depois)");
    });

    app.Map("/foo",
        (a) => {
            a.Use(async (context, next) => {
                await context.Response.WriteAsync("Middleware para o caminho /foo (antes) ");
                await next();
                await context.Response.WriteAsync("Middleware para o caminho /foo (depois) ");
            });
    });

    app.MapWhen(context => context.Request.Path.StartsWithSegments("/bar"), 
        (a) => {
            a.Use(async (context, next) => {
                await context.Response.WriteAsync("Middleware para o caminho /bar (antes) ");
                await next();
                await context.Response.WriteAsync("Middleware para o caminho /bar (depois) ");
            });
    });

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Middleware final");
    });
}

Mesmo sendo possível invocar o método next() nos middlewares definidos com Map e MapWhen, por não existir nenhum middleware seguinte, ao ser invocado é gerado um erro. Por exemplo, se a solicitação possuir um caminho /foo será gerado um fluxo assim:

Lembrando que também há a volta.

Se os métodos next() forem omitidos dos middlewares definidos em Map e MapWhen, teríamos um fluxo assim:

Primeiro middleware (antes)
Segundo middleware (antes)
Middleware para o caminho /foo (antes)
Middleware para o caminho /foo (depois)
Segundo middleware (depois)
Primeiro middleware (depois)

Criando um fluxo alternativo no pipeline

Para evitar o problema do uso do next() nos middlewares definidos em Map e MapWhen, é possível utilizar o método UseWhen, que funciona da mesma forma que o MapWhen, com a diferença que após executá-lo o fluxo do pipeline retorna ao caminho padrão:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Primeiro middleware (antes)");
        await next();
        await context.Response.WriteAsync("Primeiro middleware (depois)");
    });

    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("Segundo middleware (antes)");
        await next();
        await context.Response.WriteAsync("Segundo middleware (depois)");
    });

    app.Map("/foo",
        (a) => {
            a.Use(async (context, next) => {
                await context.Response.WriteAsync("Middleware para o caminho /foo (antes) ");
                await next();
                await context.Response.WriteAsync("Middleware para o caminho /foo (depois) ");
            });
    });

    app.UseWhen(context => context.Request.Path.StartsWithSegments("/bar"), 
        (a) => {
            a.Use(async (context, next) => {
                await context.Response.WriteAsync("Middleware para o caminho /bar (antes) ");
                await next();
                await context.Response.WriteAsync("Middleware para o caminho /bar (depois) ");
            });
    });

    app.Run(async (context) =>
    {
        await context.Response.WriteAsync("Middleware final");
    });
}

Caso a requisição tenha o caminho /bar, o resultado do pipeline será:

Primeiro middleware (antes)
Segundo middleware (antes)
Middleware para o caminho /bar (antes)
Middleware final
Middleware para o caminho /bar (depois)
Segundo middleware (depois)
Primeiro middleware (depois)

Podemos ilustrar isso na imagem abaixo:

Conclusão

Como é possível ver, a ideia por trás dos middlewares no ASP.NET Core é simples, porém muito poderosa. Muitos recursos do framework são disponibilizados para as aplicações através de middlewares.

Por isso o conhecimento do funcionamento do seu pipeline é um requisito obrigatório de todo desenvolvedor ASP.NET Core.

C# (C Sharp) - APIs REST com ASP.NET Web API
Curso de C# (C Sharp) - APIs REST com ASP.NET Web API
CONHEÇA O CURSO

Novos recursos do ASP.NET Core 2.1

No final do mês de maio deste ano, o .NET Core foi atualizado para a versão 2.1, e com ela, novos recursos foram adicionados na aplicação padrão do ASP.NET MVC. Para conhecê-los, vamos criar uma aplicação para esta versão.

C# (C Sharp) - Introdução ao ASP.NET Core
Curso de C# (C Sharp) - Introdução ao ASP.NET Core
CONHEÇA O CURSO

Criando a aplicação

Crie a aplicação com o comando abaixo:

dotnet new mvc -n AspNetCore2_1Exemplo -au Individual

Ao executar o projeto, a primeira coisa que notará é que ele carregará duas URLs:

Uma HTTP e outra HTTPS. Seguindo as recomendações de que toda aplicação deve implementar SSL, no ASP.NET Core 2.1, o template padrão já adiciona o suporte a este protocolo na aplicação. Assim, ao acessá-la, sempre seremos redirecionados para a versão HTTPS:

Outro recurso adicionado no template padrão desta versão é a exibição da mensagem indicando o uso de cookie:

Isso é algo que segue a recomendação do GDPR.

Você pode adicionar este recurso em um projeto configurando o uso do middleware CookiePolicyMiddleware, que pode ser feito, adicionando a linha abaixo na configuração da sua aplicação:

app.UseCookiePolicy();

Para ela sempre sempre redirecionada para a versão HTTPS, adicione a linha abaixo:

app.UseHttpsRedirection();

Quanto à mensagem do uso de cookies, você pode alterá-la no arquivo Views/Shared/_CookieConsentPartial.cshtml:

@using Microsoft.AspNetCore.Http.Features

@{
    var consentFeature = Context.Features.Get<ITrackingConsentFeature>();
    var showBanner = !consentFeature?.CanTrack ?? false;
    var cookieString = consentFeature?.CreateConsentCookie();
}

@if (showBanner)
{
    <nav id="cookieConsent" class="navbar navbar-default navbar-fixed-top" role="alert">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#cookieConsent .navbar-collapse">
                    <span class="sr-only">Toggle cookie consent banner</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <span class="navbar-brand"><span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span></span>
            </div>
            <div class="collapse navbar-collapse">
                <p class="navbar-text">
                    Use this space to summarize your privacy and cookie use policy.
                </p>
                <div class="navbar-right">
                    <a asp-controller="Home" asp-action="Privacy" class="btn btn-info navbar-btn">Learn More</a>
                    <button type="button" class="btn btn-default navbar-btn" data-cookie-string="@cookieString">Accept</button>
                </div>
            </div>
        </div>
    </nav>
    <script>
        (function () {
            document.querySelector("#cookieConsent button[data-cookie-string]").addEventListener("click", function (el) {
                document.cookie = el.target.dataset.cookieString;
                document.querySelector("#cookieConsent").classList.add("hidden");
            }, false);
        })();
    </script>
}

Por fim, ao registrar um usuário e acessar a página de gerenciamento da conta, é possível ver o link da opção para exportar os dados e excluir a conta:

Isso também foi implementado devido às regras do GDPR. Também por causa dessas regras, é criada uma página onde deve ser adicionado a política de privacidade da aplicação:

Ela não aparece no menu, mas se encontra no caminho: Home/Privacy

Conclusão

O ASP.Net Core 2.1 adiciona uma série de recursos que facilitam a adequação da aplicação às mais novas regras da internet, reduzindo a necessidade de codificação e preocupação com estes detalhes, deixando o programador focado apenas com a lógica de negócio do projeto.

C# (C Sharp) - Introdução ao ASP.NET Core
Curso de C# (C Sharp) - Introdução ao ASP.NET Core
CONHEÇA O CURSO

Global Tools no .NET Core 2.1

Neste artigo vou falar sobre as Global Tools, que é um recurso adicionado na versão 2.1 do .NET Core.

Uma global tool é um pacote “especial” do NuGet que contém uma aplicação console. Quando esta aplicação é instalada como uma ferramenta, o .NET Core CLI irá baixar o seu arquivo, extrair no disco e disponibilizá-la como um comando de terminal.

Nos bastidores, o .NET Core CLI está apenas adicionando o caminho da aplicação na variável de ambiente PATH do sistema. E por ser executada pelo terminal, que este recurso só é valido para as aplicações console.

Aqui não existe limitação do que a aplicação pode implementar. Ou seja: caso a aplicação console esteja executando corretamente, ela pode ser convertida para uma “global tool”.

Para entender isso na prática, vamos criar uma aplicação simples de renomeação de arquivos.

Adobe After Effects CC - Completo
Curso de Adobe After Effects CC - Completo
CONHEÇA O CURSO

Ambiente

Antes de colocarmos a mão na massa, é importante que a versão 2.1 (ou superior) do .NET Core esteja instalada na máquina.

Para este artigo, estou utilizando a versão 2.1.4 do .NET Core, em uma máquina Mac OS X.

Colocando a mão na massa

Como dito, uma global tool é criada a partir de uma aplicação console, assim, a primeira coisa que deve ser feita é criar esta aplicação:

dotnet new console -n RenomearArquivos

Em seguida o seu código deve ser definido. No nosso exemplo, ele será:

class Program
{
    static void Main(string[] args)
    {
        if(args.Count() == 0)
            Console.WriteLine("Nenhuma opção foi informada! Indique o diretório (-d), filtro (-t) e prefixo (-n)");
        else
        {
            var diretorio = String.Empty;
            var filtro = "*";
            var prefixo = String.Empty;

            for(var i = 0; i < args.Count(); i++)
            {
                switch (args[i].ToLower())
                {
                    case "-d":
                        diretorio = args[++i];
                        break;
                    case "-t":
                        filtro = args[++i];
                        break;
                    case "-n":
                        prefixo = args[++i];
                        break;
                    default:
                        Console.WriteLine($"Opção {args[i]} inválida!");
                        Environment.Exit(1);
                        break;
                }
            }

            Renomear(diretorio, filtro, prefixo);
        }

    }

static void Renomear(string diretorio, string filtro, string prefixo){

        Console.WriteLine("Arquivos renomeados:");
        foreach(var arq in Directory.GetFiles(diretorio, filtro))
        {
            var novoArquivo = Path.Combine(Path.GetDirectoryName(arq), String.Format("{0} - {1}", prefixo, Path.GetFileName(arq)));
            File.Move(arq, novoArquivo);
            Console.WriteLine(novoArquivo);
        }
    } 
} 

Como é possível notar, esta aplicação é bem simples.

Ao executá-la, veremos o seguinte resultado:

Mas como disse, qualquer aplicação console pode ser transformada em uma global tool. Infelizmente, ainda não há uma página que organize todas as global tools disponíveis, mas aqui contém ótimos exemplos de ferramentas criadas pela equipe do ASP.NET Core.

Empacotando a aplicação

Para disponibilizar a nossa aplicação como uma global tool precisamos empacotá-la em um pacote NuGet. Para isso, temos de adicionar algumas informações no arquivo de configuração do projeto (*.csproj):

<PropertyGroup>
  <OutputType>Exe</OutputType>
  <TargetFramework>netcoreapp2.1</TargetFramework>
  <PackAsTool>true</PackAsTool>
  <ToolCommandName>renarquivos</ToolCommandName>
  <Version>1.0.0</Version>
  <Description>A example of a tool for the Treinaweb Blog</Description>
</PropertyGroup>

Onde:

  • PackAsTool: Indica que a aplicação deve ser empacotada como uma global tool;
  • ToolCommandName: Indica o comando utilizado para invocar a aplicação. Caso isso não seja especificado, será utilizado o nome da aplicação;
  • Version: A versão da aplicação;
  • Description: Descrição da ferramenta.

E, deve ser criado na raiz da aplicação um arquivo chamado .nuspec onde deve ser adicionado a informação abaixo:

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
  <metadata>
    <!-- Esta parte é obrigatória -->
    <packageTypes>
      <packageType name="DotnetTool" />
    </packageTypes>
    <!-- ... -->
  </metadata>
</package>

Ela indica que o pacote a será criado deve ser tratado como uma global tool. Sem esta informação, o pacote até pode ser criado, mas não seria possível instalá-lo como uma global tool.

Com essas informações definidas, a aplicação pode ser empacotada com o comando abaixo:

dotnet pack . -c Release -o nupkg

Este comando irá compilar a aplicação para “release”, e irá gerar o arquivo de pacote dela na pasta nupkg. Para testar a ferramenta, acesse a pasta nupkg e execute o comando abaixo:

dotnet new nugetconfig

Este comando irá gerar um arquivo de configuração do NetGet (nuget.config) onde deve ser informando o caminho do arquivo NuGet que acabamos de gerar:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 <packageSources>
    <add key="local" value="." />
 </packageSources>
</configuration>

Desta forma, sempre que o comando de edição de pacotes for executado no nível desta pasta, ele também irá procurar por pacotes contidos nela.

Instalado a Global Tool

Por fim, para instalar a global tool que acabamos de definir, basta executar no nível da sua pasta, o comando abaixo:

dotnet tool install -g RenomearArquivos

Após isso, a nossa global tool estará disponível como comando no terminal:

Simples não, é? O código desta aplicação está disponível aqui.

Em artigos futuros falarei de outros recursos da versão 2.1 do .NET Core. Até lá!

ZBrush - Introdução a escultura digital
Curso de ZBrush - Introdução a escultura digital
CONHEÇA O CURSO

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.

C# (C Sharp) - Introdução ao ASP.NET Core
Curso de C# (C Sharp) - Introdução ao ASP.NET Core
CONHEÇA O CURSO

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.

C# (C Sharp) - Introdução ao ASP.NET Core
Curso de C# (C Sharp) - Introdução ao ASP.NET Core
CONHEÇA O CURSO

Conhecendo o Microsoft Orleans

Quando a Microsoft anunciou que o .NET seria aberto à comunidade em 2015, ela abriu várias bibliotecas, entre elas há o Microsoft Orleans, que é o tema deste artigo.

Este é o primeiro artigo sobre este framework, onde conheceremos a sua estrutura. No próximo artigo o utilizaremos em uma aplicação.

C# (C Sharp) Intermediário
Curso de C# (C Sharp) Intermediário
CONHEÇA O CURSO

O que é o Microsoft Orleans?

O Orleans é um framework que permite a criação de aplicações distribuídas e escalares de forma simples e direta. Sem a necessidade de aprender ou se preocupar com as complexidades de concorrência ou escalabilidade.

Atualmente na versão 2.0, este framework é utilizado por várias equipes da Microsoft, mas principalmente na área de games, onde foi utilizado para implementar os recursos de cloud de jogos como Halo 4 e 5 e Guild Wars 2.

O problema camada intermediária

Aplicações, ou serviços “cloud”, são inerentemente paralelas e distribuídas. Elas também são interativas e dinâmicas; frequentemente requerendo interação em tempo real com outras entidades.

Caso esses aplicativos tenham uma grande demanda, a sua cria criação pode ser bem complexa. Criar uma aplicação com este comportamento de forma otimizada, demanda um grande nível de conhecimento dos programadores e várias interações entre as equipes de design e arquitetura.

Atualmente, a criação de uma aplicação escalável é feita como uma composição de camadas “stateless“, onde a lógica de negócio é centrada em uma camada intermediária:

Por mais que seja possível escalar esta camada intermediária, será necessário se preocupar com o banco de dados. Pois a aplicação terá que manter os dados da camada intermediária sincronizados. Caso acesse muito o banco de dados para fazer isso, a performance do banco pode ser um gargalo para a aplicação.

Caso mantenha muitos dados na memória, durante uma atualização, os dados em algumas instâncias desta camada podem ficar defasados. Além de adicionar possíveis problemas de cache.

Orleans na camada intermediária

Orleans procura resolver os problemas que vimos acima com os seus atores virtuais. Ele permite, de maneira intuitiva, a criação de várias entidades de domínio – atores virtuais, que aparentam ser objetos .NET de várias aplicações distribuídas e insoladas dentro de um cluster (silo):

Essas entidades de domínio são chamadas pelo Orleans de grãos (grains) e trata-se de classes .NET simples, que implementam um ou mais interfaces da biblioteca, que as definem como uma classe “grão”.

Grãos individuais são instâncias de classes “grãos” definidas pela aplicação, que são criadas automaticamente pelo Orleans em tempo de execução, conforme as solicitações. Esses grãos podem ser qualquer tipo de entidade, como: pessoa, pedido, cliente, etc; só é requerido que ele tenha um código de identificação, que pode ser definido, como: um e-mail, um código ou uma identificação do banco. O Orleans garante que cada grão será executado em uma thread separada, que o protegerá de problemas de concorrência ou conflito.

No mundo dos microservices, Orleans é utilizado como uma estrutura para implementar um microservice que pode ser implementado e gerenciado por uma solução de microservice escolhida pelo desenvolvedor.

Grãos e silos

Um grão pode persistir seu estado na memória ou no banco ou em uma combinação de ambos. Dentro do silo – que é o nome dado para o servidor criado pelo Orleans ao executar a aplicação – um grão pode chamar qualquer outro grão ou pode ser chamado pelo cliente (frontend) utilizando apenas o seu identificador, sem a necessidade de criar ou instanciá-lo.

Não importando onde esteja salvo o estado do grão, o framework fará parecer que ele esteve o tempo todo em memória. Mas na realidade, caso um grão fique muito tempo inativo, ele é removido da memória e seu estado é salvo no banco.

Todo este ciclo é transparente para o código, o que libera o programador de se preocupar com esses detalhes.

Outra vantagem deste comportamento é que o Orleans consegue lidar com uma falha de forma automática. Ao notar que um nó falou, ele reinstala nos demais nós os dados presentes no nó que falhou.

Todos esses detalhes podem parecer complexos em um primeiro momento. No próximo artigo, quando codificaremos uma aplicação simples, eles ficarão mais claros.

Até lá.

C# (C Sharp) Básico
Curso de C# (C Sharp) Básico
CONHEÇA O CURSO

Utilizando o Micro ORM PetaPoco em uma aplicação ASP.NET Core

Como a maioria dos desenvolvedores .NET, quase todas as minhas aplicações fazem uso do Entity Framework. Fazer parte do ecossistema, ser integrado ao Visual Studio, possuir vários recursos, são sempre vantagens que nos leva a optar por seu uso.

Mas, as vezes, mesmo estas vantagens podem aborrecer. Pois quando que ser algo fora do padrão, o processo pode ser muito verboso ou caso o projeto seja muito simples, caímos na situação de matar uma barata com um canhão.

Para estes casos, felizmente, temos os micro-frameworks ORM. Em artigos passados, já falei sobre o Dapper e o ServiceStack.OrmLite.

No artigo de hoje abordarei o PetaPoco.

ZBrush - Introdução a escultura digital
Curso de ZBrush - Introdução a escultura digital
CONHEÇA O CURSO

PetaPoco

O PetaPoco é um micro-framework ORM, criado por Brad Robinson, da topten software, que agora é mantido pelo grupo Collaborating Platypus.

Focado em rapidez e facilidade, o PetaPoco é uma solução de um arquivo, por isso é pequeno e não possui dependências. Como o nome sugere, trabalha com classes POCO, que requerem pouca configuração. Para facilitar, também inclui templates T4, que geram automaticamente classes POCO e suporta configurações Fluent.

No momento da criação deste artigo, ele suporta os bancos: SQL Server, SQL Server CE, Microsoft Access, SQLite, MySQL, MariaDB, Firebird e PostgreSQL (Oracle é suportado, mas não possui testes de integração).

Para exemplificar o seu uso, vamos ver um exemplo dele em uma aplicação ASP.NET Core.

Criando a aplicação

No terminal, digite o código abaixo para criar uma aplicação chamada AspNetCorePetaPoco:

dotnet new mvc -n AspNetCorePetaPoco

Acesse a pasta da aplicação e adicione nela o pacote do PetaPoco:

dotnet add package PetaPoco.Core.Compiled

Por fim, é necessário adicionar a biblioteca do provedor que iremos utilizar:

dotnet add package MySql.Data

Não esqueça de aplicar o restore:

dotnet restore

Agora podemos começar a configuração do PetaPoco.

Criando a classe POCO

Para esta entidade será utilizado a classe POCO (aka Model/Entidade) abaixo:

using System;

namespace AspNetCorePetaPoco.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Quantity { get; set; }
        public double Price { get; set; }
    }
}

Caso queria modificar o comportamento padrão, pode ser utilizado fluent, ou definir os atributos decoradores, por exemplo:

[TableName("Pessoas")]
[PrimaryKey("Id", AutoIncrement = false)]
public class Pessoa
{
    [Column]
    public Guid Id { get; set; }

    [Column]
    public string Nome { get; set; }

    [Column]
    public int Idade { get; set; }

    [Column]
    public int Altura { get; set; }

    [Column(Name = "Data Nascimento")]
    public DateTime? Nascimento { get; set; }

    [Ignore]
    public string NomeIdade => $"{Nome} tem a idade {Idade}";
}

Neste arquivo, me aterei a classe Product.

Configurando o acesso ao banco de dados

Assim como os outros micro-ORM, o PetaPoco não define uma classe de configuração, o acesso ao banco de dados pode ser obtido através do método DataBase da classe PetaPoco:

var db = new PetaPoco.Database("connectionStringName");

Ou através de fluent:

var db = DatabaseConfiguration.Build()
         .UsingConnectionString("Server=127.0.0.1;Uid=root;Pwd=1234;Database=PetaPocoExample;Port=3306")
         .UsingProvider<MySqlDatabaseProvider>()
         .UsingDefaultMapper<ConventionMapper>(m =>
         {
             m.InflectTableName = (inflector, s) => inflector.Pluralise(inflector.Underscore(s));
             m.InflectColumnName = (inflector, s) => inflector.Underscore(s);
         })
         .Create();

Ele também não cria as estruturas no banco de dados, então elas devem ser criadas “na mão”. Neste exemplo, estou definindo o código de criação da tabela em um arquivo chamado mysql_init.sql, que foi salvo em uma pasta chamada Scripts e contém o conteúdo abaixo:

CREATE TABLE IF NOT EXISTS Product (

    id              bigint AUTO_INCREMENT NOT NULL,
    name            varchar(500) NOT NULL,
    quantity        int NOT NULL,
    price           decimal(10,2) NOT NULL, 
    PRIMARY KEY (id)
) ENGINE=INNODB;

Agora, iremos definir a execução deste script e o acesso ao banco, em um repositório abstrato:

using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using PetaPoco;
using PetaPoco.Providers;

namespace AspNetCorePetaPoco.Repositories
{
    public abstract class AbstractRepository<T>
    {
        private string _connectionString;
        private IDatabase _db;
        protected IDatabase Db => _db;
        public AbstractRepository(IConfiguration configuration){
            _connectionString = configuration.GetValue<string>("DBInfo:ConnectionString");

            _db =  DatabaseConfiguration.Build()
                    .UsingConnectionString(_connectionString)
                    .UsingProvider<MySqlDatabaseProvider>()
                    .Create();

            _db.Execute(LoadTextResource("mysql_init.sql"));
        }
        public abstract void Add(T item);
        public abstract void Remove(int id);
        public abstract void Update(T item);
        public abstract T FindByID(int id);
        public abstract IEnumerable<T> FindAll();

        private string LoadTextResource(string name)
        {
            var path = System.IO.Path.Combine(System.IO.Directory.GetCurrentDirectory(), "Scripts", name);
            System.Console.WriteLine(path);
            System.IO.StreamReader r = new System.IO.StreamReader(path);
            string str = r.ReadToEnd();
            r.Close();

            return str;
        }
    }
}

Note que no método construtor um objeto de _Db foi criado:

_db =  DatabaseConfiguration.Build()
                    .UsingConnectionString(_connectionString)
                    .UsingProvider()
                    .Create();

Acima, é importante que o Provider reflita corretamente a biblioteca de acesso instalada.

Agora para finalizar a configuração, vamos definir o repositório abaixo:

using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using PetaPoco;
using PetaPoco.Providers;
using AspNetCorePetaPoco.Models;

namespace AspNetCorePetaPoco.Repositories
{
    public class ProductRepository: AbstractRepository
    {
        public ProductRepository(IConfiguration configuration): base(configuration) { }

        public override void Add(Product item)
        {
            Db.Insert(item);
        }
        public override void Remove(int id)
        {
            var product = Db.Single(id);
            Db.Delete(product);
        }
        public override void Update(Product item)
        {
            Db.Update(item);
        }
        public override Product FindByID(int id)
        { 
            return Db.Single(id);
        }
        public override IEnumerable FindAll()
        { 
            return Db.Query("SELECT * FROM Products");
        }
    }
}

Note que o PelaPoco gera as queries SQL com base na classe POCO informada no parâmetro dos métodos:

Db.Insert(item);

E quando ele não consegue gerar, deve ser informado a query:

return Db.Query("SELECT * FROM Products");

Usando o PetaPoco

Para exemplificar o uso do PetaPoco, crie o controller abaixo:

using System.Linq;
using AspNetCorePetaPoco.Models;
using AspNetCorePetaPoco.Repositories;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;

namespace AspNetCorePetaPoco.Controllers
{
    public class ProductController : Controller
    {
        private readonly ProductRepository productRepository;

        public ProductController(IConfiguration configuration){
            productRepository = new ProductRepository(configuration);
        }

        // GET: Products
        public ActionResult Index()
        {
            return View(productRepository.FindAll().ToList());
        }

        // GET: Products/Details/5
        public ActionResult Details(int? id)
        {
            if (id == null)
            {
                return StatusCode(StatusCodes.Status404NotFound);
            }
            Product product = productRepository.FindByID(id.Value);
            if (product == null)
            {
                return StatusCode(StatusCodes.Status404NotFound);
            }
            return View(product);
        }

        // GET: Products/Create
        public ActionResult Create()
        {
            return View();
        }

        // POST: Products/Create
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind("Id,Name,Quantity,Price")] Product product)
        {
            if (ModelState.IsValid)
            {
                productRepository.Add(product);
                return RedirectToAction("Index");
            }

            return View(product);
        }

        // GET: Products/Edit/5
        public ActionResult Edit(int? id)
        {
            if (id == null)
            {
                return StatusCode(StatusCodes.Status400BadRequest);
            }
            Product product = productRepository.FindByID(id.Value);
            if (product == null)
            {
                return StatusCode(StatusCodes.Status404NotFound);
            }
            return View(product);
        }

        // POST: Products/Edit/5
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit([Bind("Id,Name,Quantity,Price")] Product product)
        {
            if (ModelState.IsValid)
            {
                productRepository.Update(product);
                return RedirectToAction("Index");
            }
            return View(product);
        }

        // GET: Products/Delete/5
        public ActionResult Delete(int? id)
        {
            if (id == null)
            {
                return StatusCode(StatusCodes.Status400BadRequest);
            }
            Product product = productRepository.FindByID(id.Value);
            if (product == null)
            {
                return StatusCode(StatusCodes.Status404NotFound);
            }
            return View(product);
        }

        // POST: Products/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteConfirmed(int id)
        {
            productRepository.Remove(id);
            return RedirectToAction("Index");
        }
    }
}

Também crie as views para as actions acima e então podemos ver o sistema funcionando:

Print da aplicação criada no artigo

C# (C Sharp) - TDD
Curso de C# (C Sharp) - TDD
CONHEÇA O CURSO

Criando um Web Service com o ServiceStack – Parte 2

Dando continuidade ao estudo da biblioteca ServiceStack, neste artigo modificaremos o web service criado na primeira parte.

Os web services criados com essa biblioteca geralmente fazem uso dos outros recursos. Um deles é o ServiceStack.OrmLite, que é um micro framework ORM, que também já vimos anteriormente nesse artigo. Assim, vamos adicioná-lo na aplicação criada na primeira parte desse artigo.

C# (C Sharp) - Introdução ao ASP.NET Core
Curso de C# (C Sharp) - Introdução ao ASP.NET Core
CONHEÇA O CURSO

Colocando a mão na massa

Inicialmente iremos definir a classe responsável pela resposta do web service. Desta forma, dentro do projeto, na pasta ServiceModel, será criado a classe chamada TodoRespose, contendo o código abaixo:

using ServiceStack;

namespace ServiceStackExample.ServiceModel {
    public class TodoResponse
    {
        public object Result { get; set; }

        public ResponseStatus ResponseStatus { get; set; }
    }
}

A criação desta classe não é algo obrigatório, como o artigo anterior pode ter implicitado, mas gosto de separar a classe DTO do seu response.

Ainda dentro da pasta ServiceModel adicione a classe DTO Todo, conforme o código abaixo:

using ServiceStack;

namespace ServiceStackExample.ServiceModel {

    [Route("/todo")]
    [Route("/todo/{Id}")]
    public class Todo
    {
        public int Id { get; set; }
        public string Text { get; set; }
        public bool Done { get; set; }
    }
}
C# (C Sharp) Básico
Curso de C# (C Sharp) Básico
CONHEÇA O CURSO

Definindo o ServiceStack.OrmLite

Antes de criarmos um service para a classe DTO Todo, vamos configurar o acesso ao banco de dados.

A primeira coisa que deve ser feita é adicionar a referência da biblioteca no projeto:

dotnet add package ServiceStack.OrmLite.Sqlite.Core

Acima estou usando o pacote do banco de dados que irei utilizar neste artigo. Você pode ver as demais versões disponíveis aqui.

Não se esqueça de aplicar o restore no projeto:

dotnet restore

A classe POCO utilizada será a DTO Todo, assim já iremos definir o repositório dela.

Inicialmente criaremos uma classe abstrata, onde o acesso pelo OrmLite será definido:

using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using ServiceStack.OrmLite;

namespace ServiceStackExample.Repositories
{
    public abstract class AbstractRepository<T>
    {
        private string _connectionString;
        private OrmLiteConnectionFactory _dbFactory;
        protected OrmLiteConnectionFactory DbFactory => _dbFactory;
        public AbstractRepository(IConfiguration configuration){
            _connectionString = configuration.GetValue<string>("DBInfo:ConnectionString");

            _dbFactory = new OrmLiteConnectionFactory(_connectionString, SqliteDialect.Provider);
        }
        public abstract void Add(T item);
        public abstract void Remove(int id);
        public abstract void Update(T item);
        public abstract T FindByID(int id);
        public abstract IEnumerable<T> FindAll();
    }
}

E a partir dele, será criado um repositório para a classe Todo:

using Microsoft.Extensions.Configuration;
using ServiceStackExample.ServiceModel;
using ServiceStack.OrmLite;
using System.Collections.Generic;

namespace ServiceStackExample.Repositories
{
    public class TodoRepository: AbstractRepository<Todo>
    {
        public TodoRepository(IConfiguration configuration): base(configuration) { }

        public override void Add(Todo item)
        {
            using (var db = DbFactory.Open())
            {
                if (db.CreateTableIfNotExists<Todo>())
                {
                    db.Insert(item);
                }
            }
        }
        public override void Remove(int id)
        {
            using (var db = DbFactory.Open())
            {
                db.Delete<Todo>(p => p.Id == id);
            }
        }
        public override void Update(Todo item)
        {
            using (var db = DbFactory.Open())
            {
                db.Update(item);
            }
        }
        public override Todo FindByID(int id)
        { 
            using (var db = DbFactory.Open())
            {
                return db.SingleById<Todo>(id);
            }
        }
        public override IEnumerable<Todo> FindAll()
        {
            using (var db = DbFactory.Open())
            { 
                if (db.CreateTableIfNotExists<Todo>())
                {
                    return db.Select<Todo>();
                }

                return db.Select<Todo>();
            }
        }
    }
}

Agora podemos definir o service.

Criando a classe Service

Agora na pasta ServiceInterface adicione uma classe chamada TodoService, contendo o código abaixo:

using Microsoft.Extensions.Configuration;
using ServiceStack;
using ServiceStackExample.Repositories;
using ServiceStackExample.ServiceModel;

namespace ServiceStackExample.ServiceInterface {
    public class TodoService: Service
    {
        private readonly TodoRepository todoRepository;

        public TodoService(IConfiguration configuration){
            todoRepository = new TodoRepository(configuration);
        }

        public object Get(Todo todo)
        {

            if(todo.Id != default(int))
                return todoRepository.FindByID(todo.Id);

            return todoRepository.FindAll();
        }

        public Todo Post(Todo todo){
            todoRepository.Add(todo);

            return todo;
        }

        public Todo Put(Todo todo){
            todoRepository.Update(todo);

            return todo;
        }

        public Todo Delete(Todo todo){
            todoRepository.Remove(todo.Id);

            return todo;
        }
    }
}

Diferente do artigo anterior, agora estamos definindo métodos para tratar todos os principais métodos do HTTP: GET, POST, PUT e DELETE.

Com isso definido, agora só é necessário criar a interface.

Interface gráfica

Como faltei em todas as aulas de front-end :P, nem vou explicar aqui os códigos deste detalhe, você pode baixar o projeto clicando aqui.

Após executar a aplicação, ela será exibida da seguinte forma:

Interface da aplicação

Com o código disponibilizado, você pode testá-la, para ver como ficou.

Um abraço!

C# (C Sharp) - ASP.NET MVC
Curso de C# (C Sharp) - ASP.NET MVC
CONHEÇA O CURSO

Criando um Web Service com o ServiceStack – Parte 1

No artigo passado conhecemos o ServiceStack e vimos um exemplo do ServiceStack.OrmLite. Como comentado no artigo anterior, o ServiceStack possui uma gama de recursos, criados para substituir os frameworks WCF, Web API, ASP.NET MVC.

Assim, continuando o seu estudo, hoje veremos como criar uma aplicação REST simples em ASP.NET Core.

Para este artigo a aplicação terá apenas rota que irá retornar o que o usuário indicar. No próximo iremos integrá-la ao ServiceStack.OrmLite.

C# (C Sharp) - Introdução ao ASP.NET Core
Curso de C# (C Sharp) - Introdução ao ASP.NET Core
CONHEÇA O CURSO

Entendo o projeto do ServiceStack

Para o Visual Studio, o ServiceStack possui um template (pode ser baixado aqui) que facilita a criação do web service.

Ao instalar este template e criar uma aplicação ServiceStack ASP.NET Empty, será criada uma solução com 4 projetos:

  • Projeto: Projeto ASP.NET vazio. É o projeto “host”, onde deve ser adicionado os recursos web, como: views, arquivos js, css, imagens, fontes, etc;
  • Projeto.ServiceInterface: Projeto que contém os serviços e a camada de negócio da aplicação.
  • Projeto.ServiceModel: Projeto que contém as classes DTO da aplicação. Essas classes definem os “contratos” dos services.
  • Projeto.Test: Projeto que contém os testes de unidade da aplicação.

Como o projeto deste artigo será criado com o .NET Core em um ambiente Unix, ele não contém múltiplos projetos, apenas pastas para separar as camadas ServiceInterface, e ServiceModel.

Então vamos colocar a mão na massa!

Colocando a mão na massa

No o .NET Core instalado na máquina, no terminal crie uma aplicação ASP.NET Core vazia com o comando abaixo:

dotnet new web -n ServiceStackExample

Acesse a pasta do projeto criado e adicione nele a dependência do ServiceStack:

dotnet add package ServiceStack.Core

Pronto, o nosso projeto estará criado e com o ServiceStack referenciado.

Criando as classes DTO

Dentro do projeto, crie uma pasta chamada ServiceModel e dentro dela crie uma classe chamada CategoryRespose, contendo o código abaixo:

using ServiceStack;

namespace ServiceStackExample.ServiceModel {
    public class CategoryResponse
    {
        public string Result { get; set; }

        public ResponseStatus ResponseStatus { get; set; }
    }
}

Como o nome indica, esta classe será responsável pela resposta do web service. Ela só necessita de uma propriedade Result, mas para que exceções também sejam exibidas no navegador, é necessário adicionar propriedade ResponseStatus.

Agora dentro da mesma pasta adicione uma classe Category, contendo o código abaixo:

using ServiceStack;

namespace ServiceStackExample.ServiceModel {

    [Route("/categories")]
    [Route("/categories/{Name}")]
    public class Category
    {
        public string Name { get; set;}
    }
}

Esta classe irá tratar as solicitações do web service para as URLs definidas nela:

[Route("/categories")]
[Route("/categories/{Name}")]

O valor {Name} definido na última URL acima, será atribuído a propriedade Name da classe.

Criando a classe Service

Agora crie no projeto a pasta ServiceInterface, e a ela adicione uma classe chamada CategoryService, contendo o código abaixo:

using ServiceStack;
using ServiceStackExample.ServiceModel;

namespace ServiceStackExample.ServiceInterface {
    public class CategoryService: Service
    {
        public object Any(Category  request){
            return new CategoryResponse { Result = $"Categoria: {request.Name}" };
        }
    }
}

Como o nome indica, esta classe é a implementação do “serviço” das classes que definimos na pasta ServiceModel. Nela pode ser definidos métodos para tratar os métodos do HTTP: GET, POST, PUT, DELETE, etc; Esses métodos devem ter o mesmo nome dos métodos do HTTP.

Ou seja, para criar um método que será chamado quando houver uma solicitação GET, o método deve ser nomeado como Get. Caso queira tratar uma solicitação POST, o método deve ser nomeado como Post; e assim por diante.

Também pode ser definido o método Any que é para qualquer solicitação, que é o que foi feito na classe acima.

Iniciando o serviço

Para que o serviço seja iniciado e as solicitações que definimos com o ServiceStack sejam tratadas pela aplicação, temos que criar uma classe AppHost, contendo o conteúdo abaixo:

using Funq;
using ServiceStack;

namespace ServiceStackExample
{
   public class AppHost: AppHostBase {
       public AppHost(): base("Treinaweb web Services", typeof(ServiceInterface.CategoryService).Assembly){}

        public override void Configure(Container container)
        {    
        }
    }
}

A classe acima está apenas registrando o nosso serviço, mas no seu método configure pode ser configurado outros recursos, como o ServiceStatck.OrmLite, Cache, Redis, Autenticação, etc;

Por fim, esta classe precisa ser chamada no método Configure da classe Startup:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    app.UseServiceStack(new AppHost());
}

Pronto, o nosso serviço já está criado e configurado e podemos testá-lo.

Executando o serviço

No terminal execute a aplicação:

dotnet run

Aí no navegador acesse o serviço: http://localhost:5000/categories/Mouse:

Print do exemplo do Service Stack Funcionando.

Tudo certo. No próximo artigo trabalharemos mais a aplicação.

Até a próxima!

C# (C Sharp) - ASP.NET MVC
Curso de C# (C Sharp) - ASP.NET MVC
CONHEÇA O CURSO

Utilizando o Micro ORM ServiceStack.OrmLite em uma aplicação ASP.NET Core

O ServiceStack é um framework leve desenvolvido “sob” o ASP.NET que permite criar web services e aplicações web. Composto por vários serviços, podemos dizer que o ServiceStack é uma alternativa mais leve dos frameworks WCF, Web API, ASP.NET MVC.

Entre seus recursos podemos destacar:

  • Web Services Framework: REST, SOAP e Message Queuing;
  • JSON Serializer: Serialização automática de JSON, CSV e JSV;
  • ORMLite: um micro framework ORM;
  • Injeção de dependência;
  • Logging API;
  • Autenticação e autorização;
  • C# Redis Client.

Geralmente os seus recursos são utilizados em conjunto, mas neste artigo iremos abordar apenas o ORMLite, como uma forma de introdução aos recursos do ServiceStack.

C# (C Sharp) - Introdução ao ASP.NET Core
Curso de C# (C Sharp) - Introdução ao ASP.NET Core
CONHEÇA O CURSO

ServiceStack.OrmLite

O objetivo do OrmLite é fornecer um wrapper de configuração simples, DRY e agnóstico; que mantém uma alta afinidade com o SQL, expondo API intuitivas que geram expressões SQL para classes POCOs desconectadas.

Esta abordagem facilita o acesso aos dados, tornando óbvio qual é o SQL gerado, e quando é executado; enquanto mitiga qualquer comportamento inesperado, comuns em ORMs mais pesadas.

O OrmLite foi criado visando os seguintes objetivos:

  • Fornecer um conjunto de métodos de extensão leves para as interfaces System.Data.*;
  • Mapear classes POCO para tabelas do banco de dados, de forma clara, livre de convenções e necessidade de atributos;
  • Criação e exclusão de tabelas utilizando apenas definições de classes POCO;
  • Simplicidade: API de acesso simples;
  • Alto desempenho: com suporte a índices, text, blobs, etc;
  • Entre os mais rápidos micro ORM para .NET
  • Poder e flexibilidade: com acesso a interface IDbCommand e expressões SQL;
  • Multiplataforma: suporta vários bancos de dados (Atualmente: Sql Server, Sqlite, MySql, PostgreSQL, Firebird), tanto no .NET Standard quanto no .NET Core;

No OrmLite uma classe é igual a uma tabela. Não há nenhum comportamento escondido, a query criada pode até retornar resultados diferentes da classe POCO utilizada para criá-la, mas apenas se isso for a opção do desenvolvedor. Por exemplo, quando se quer listar apenas alguns campos da tabela.

Por padrão, tipos complexos (não escalares) são tratados como text ou blob. Mas a API também suporta relacionamentos, podemos persistir dados relacionados de forma simples.

Para exemplificar o seu uso, vamos ver um exemplo dele em uma aplicação ASP.NET Core.

Criando a aplicação

No terminal digite o código abaixo para criar uma aplicação chamada AspNetCoreOrmLite:

dotnet new mvc -n AspNetCoreDapper

Agora, adicione o pacote do OrmLite:

dotnet add package ServiceStack.OrmLite.Sqlite.Core

Acima estou usando o pacote do banco de dados que irei utilizar neste artigo. Você pode ver aqui as demais versões disponíveis.

Não se esqueça de aplicar o restore no projeto:

dotnet restore

Com isso já podemos começar a nossa configuração OrmLite, iniciando pela criação da entidade/classe POCO.

Criando a classe POCO

Para este exemplo será utilizado a classe abaixo:

using System;

namespace AspNetCoreOrmLite.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Quantity { get; set; }
        public double Price { get; set; }
    }
}

Configurando o acesso ao banco de dados

O OrmLite não define uma classe de configuração, o acesso ao banco de dados pode ser obtido com um objeto da classe OrmLiteConnectionFactory:

var dbFactory = new OrmLiteConnectionFactory(
    connectionString,  
    SqliteDialect.Provider);

Caso queria utilizar IOC, ela pode ser registrada como singleton:

container.Register<IDbConnectionFactory>(c => 
    new OrmLiteConnectionFactory(connectionString, SqliteDialect.Provider));

Para o nosso exemplo, irei configurar o acesso com base em repositórios:

using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using ServiceStack.OrmLite;

namespace AspNetCoreOrmLite.Repositories
{
    public abstract class AbstractRepository<T>
    {
        private string _connectionString;
        private OrmLiteConnectionFactory _dbFactory;
        protected OrmLiteConnectionFactory DbFactory => _dbFactory;
        public AbstractRepository(IConfiguration configuration){
            _connectionString = configuration.GetValue<string>("DBInfo:ConnectionString");

            _dbFactory = new OrmLiteConnectionFactory(_connectionString, SqliteDialect.Provider);
        }
        public abstract void Add(T item);
        public abstract void Remove(int id);
        public abstract void Update(T item);
        public abstract T FindByID(int id);
        public abstract IEnumerable<T> FindAll();
    }
}

Note que no método construtor um objeto de DbFactory foi criado:

_dbFactory = new OrmLiteConnectionFactory(_connectionString, SqliteDialect.Provider);

Por mais que seja aceito outro, o provider definido nesta classe deve ser do banco do pacote adicionado na aplicação. Caso seja referenciado outro banco, um erro será apresentado.

Agora para finalizar a configuração, vamos definir o repositório abaixo:

namespace AspNetCoreOrmLite.Repositories
{
    public class ProductRepository: AbstractRepository
    {
        public ProductRepository(IConfiguration configuration): base(configuration) { }

        public override void Add(Product item)
        {
            using (var db = DbFactory.Open())
            {
                if (db.CreateTableIfNotExists())
                {
                    db.Insert(item);
                }
            }
        }
        public override void Remove(int id)
        {
            using (var db = DbFactory.Open())
            {
                db.Delete(p => p.Id == id);
            }
        }
        public override void Update(Product item)
        {
            using (var db = DbFactory.Open())
            {
                db.Update(item);
            }
        }
        public override Product FindByID(int id)
        { 
            using (var db = DbFactory.Open())
            {
                return db.SingleById(id);
            }
        }
        public override IEnumerable FindAll()
        { 
                if (db.CreateTableIfNotExists())
                {
                    return db.Select();
                }

                return db.Select();
        }
    }
}

Note que o OrmLite gera as queries SQL com base na classe POCO informadas nos seus métodos genéricos:

using (var db = DbFactory.Open())
{
    db.Delete(p => p.Id == id);
}

Ou de acordo com o parâmetro do método:

using (var db = DbFactory.Open())
{
    db.Insert(item);
}

Quando há retorno de dados:

using (var db = DbFactory.Open())
{
    return db.SingleById(id);
}

Eles podem ser atribuídos a um objeto da classe POCO como acima, ou a outro objeto caso seja filtrado:

var q = db.From()
          .Where(x => x.Quantity  new { x.Id, x.Name });

Dictionary results = db.Dictionary(q);

Acima também é possível reparar que o filtro pode ser criado com LINQ e a query resultante passada para o OrmLite.

Ou isso pode ser definido como uma query SQL:

var tracks = db.Select<Track>("SELECT * FROM track WHERE Artist = @artist AND Album = @album", new { artist = "Nirvana", album = "Heart Shaped Box" });

Há muitas outras opções de métodos e criação de queries, você pode ver no repositório do framework.

Usando o OrmLite

Para exemplificar o uso do OrmLite, crie o controller abaixo:

using System.Linq;
using AspNetCoreOrmLite.Models;
using AspNetCoreOrmLite.Repositories;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;

namespace AspNetCoreOrmLite.Controllers
{
    public class ProductController : Controller
    {
        private readonly ProductRepository productRepository;

        public ProductController(IConfiguration configuration){
            productRepository = new ProductRepository(configuration);
        }

        // GET: Products
        public ActionResult Index()
        {
            return View(productRepository.FindAll().ToList());
        }

        // GET: Products/Details/5
        public ActionResult Details(int? id)
        {
            if (id == null)
            {
                return StatusCode(StatusCodes.Status404NotFound);
            }
            Product product = productRepository.FindByID(id.Value);
            if (product == null)
            {
                return StatusCode(StatusCodes.Status404NotFound);
            }
            return View(product);
        }

        // GET: Products/Create
        public ActionResult Create()
        {
            return View();
        }

        // POST: Products/Create
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind("Id,Name,Quantity,Price")] Product product)
        {
            if (ModelState.IsValid)
            {
                productRepository.Add(product);
                return RedirectToAction("Index");
            }

            return View(product);
        }

        // GET: Products/Edit/5
        public ActionResult Edit(int? id)
        {
            if (id == null)
            {
                return StatusCode(StatusCodes.Status400BadRequest);
            }
            Product product = productRepository.FindByID(id.Value);
            if (product == null)
            {
                return StatusCode(StatusCodes.Status404NotFound);
            }
            return View(product);
        }

        // POST: Products/Edit/5
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit([Bind("Id,Name,Quantity,Price")] Product product)
        {
            if (ModelState.IsValid)
            {
                productRepository.Update(product);
                return RedirectToAction("Index");
            }
            return View(product);
        }

        // GET: Products/Delete/5
        public ActionResult Delete(int? id)
        {
            if (id == null)
            {
                return StatusCode(StatusCodes.Status400BadRequest);
            }
            Product product = productRepository.FindByID(id.Value);
            if (product == null)
            {
                return StatusCode(StatusCodes.Status404NotFound);
            }
            return View(product);
        }

        // POST: Products/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteConfirmed(int id)
        {
            productRepository.Remove(id);
            return RedirectToAction("Index");
        }
    }
}

Também crie as views para as actions acima e então podemos ver o sistema funcionando:

Aplicação AspNet Core OrmLite

Você pode baixar o código desta aplicação clicando aqui.

C# (C Sharp) - APIs REST com ASP.NET Web API
Curso de C# (C Sharp) - APIs REST com ASP.NET Web API
CONHEÇA O CURSO