Documentação Para ASP.NET Web API com Swagger-Swashbuckle

Apesar de extremamente importante, a documentação de APIs não costuma ter a atenção que merece. Uma documentação bem feita aumenta as chances da API ser adotada por outros desenvolvedores e diminui custos de suporte (já que o desenvolvedor-cliente terá mais condições de resolver eventuais problemas sozinho).

O Swashbuckle é um pacote para projetos WebAPI que faz a geração automática de documentação. Para isso ele utiliza a classe ApiExplorer e o framework para geração de documentação de APIs Swagger. A classe ApiExplorer gera meta-informações sobre os métodos de todos os ApiControllers do projeto enquanto o Swagger exibe estas informações em uma interface bastante amigável através do módulo Swagger-UI.

Utilizando o Swashbuckle

Para demonstrar o uso do Swashbuckle, criei um projeto ASP.NET Web Application chamado Swagger e selecionei a opção Web API. Este projeto possui um controller de exemplo chamado ValuesController que será utilizado para demonstrar como documentar a sua API. Depois de criado o projeto, instale o pacote Swashbuckle através do NuGet Manager ou diretamente pelo Package Manager Console com o seguinte comando:

Install-Package Swashbuckle

Apenas com a instalação deste pacote já temos uma versão inicial de nossa documentação pronta para uso. Compile o projeto e acesse a URL http://localhost:[PORTA]//swagger/ui/index. Você deverá ver algo como a figura abaixo:

Veja que todos os serviços de nossa API (definidos em ValuesController) podem ser analisados de forma fácil em uma interface agradável. Além disso, é possível fazer requisições de teste especificando-se os parâmetros adequados para cada tipo de serviço e clicando no botão Try it out!.

Melhorando a descrição de cada serviço

O Swagger pode utilizar comentários XML para complementar as informações mostradas na figura acima. Isto facilita a atualização da documentação pois assim que um serviço for modificado e seus comentários forem atualizados a documentação da API também estará atualizada. Fiz algumas modificações no método POST de ValuesController e adicionei comentários conforme pode-se ver na figura abaixo (não esqueça de remover o atributo [Authorize] aplicado à classe):

public class ValuesController : ApiController {
   // GET api/values
   public IEnumerable<string> Get()
   {
      return new string[] { "value1", "value2" };
   }

   // GET api/values/5
   public string Get(int id)
   {
      return "value";
   }

   /// <summary>
   /// Verifica se a PropriedadeA é um valor entre 0 e 100. Retorna a mensagem contida em PropriedadeB
   /// </summary>
   /// <remarks>A PropriedadeA com valor menor que zero é considerada inválida.</remarks>
   /// <response code="400">Bad request</response>
   /// <response code="500">Internal Server Error</response>
   public IHttpActionResult Post([FromBody]Value value)
   {
      if (value.PropriedadeA < 0)
         return InternalServerError();

      if (value.PropriedadeA > 100)
         return BadRequest();

      return Ok(value.PropriedadeB);
    }

    // PUT api/values/5
    public void Put(int id, [FromBody]string value)
    {
    }

    // DELETE api/values/5
    public void Delete(int id)
    {
    }
    }

Ao verificar novamente a página de documentação você observará que nenhuma mudança apareceu. Para que o Swagger possa utilizar os comentários na documentação é preciso configurar o Swashbuckle para que utilize comentários XML. Para fazer isso, abra o arquivo SwaggerConfig.cs (que foi criado na pasta App_Start durante a instalação do pacote) e remove os comentários da seguinte linha:

c.IncludeXmlComments(GetXmlCommentsPath());

Agora, edite esta linha para:

c.IncludeXmlComments(System.String.Format(@"{0}\bin\Swagger.XML",System.AppDomain.CurrentDomain.BaseDirectory));

Conforme explicado no próprio arquivo config, este comando indica ao Swashbuckle que leia os comentários do arquivo SwaggerXML que deverá estar dentro do diretório bin do nosso projeto. Para gerar este arquivo, acesse as propriedades do seu projeto e clique na aba Build. Na seção Output marque a opção XML documentation file e certifique-se de que o caminho do arquivo é bin\Swagger.XML.

Compile o projeto e acesse a documentação. Veja que agora o método POST possui novas informações obtidas dos comentários.

O Visual Studio vai exibir um warning para todos os métodos que não estiverem com comentários XML – Missing XML comment for pubicly visible type or member. Isto pode ser bem incômodo se o projeto já possuir muitos métodos. Para remover estes warnings acesse novamente as propriedades do projeto na aba Build. Na seção Errors and warnings insira 1591 no campo Suppress warnings. Será necessário compilar o projeto novamente para que os warnings desapareçam.

Conclusão

Com este tutorial você conseguirá gerar dinamicamente a documentação para sua API e ao mesmo tempo ter uma interface para criar requisições de testes. O Swashbuckle oferece diversas configurações avançadas que permitem integrar na documentação informações relativas ao método de autenticação/autorização utilizados e customização da página HTML gerada (veja a documentação completa na página no Git do projeto Swashbuckle.

 

Escrito por Rafael Companhoni

Um pouco mais de Knockout.js

O Knockout.js é uma biblioteca JavaScript open source desenvolvida e mantida por Steve Sanderson, colaborador da Microsoft, cujo principal objetivo é oferecer suporte para criação de interfaces dinâmicas baseadas no padrão MVVM. A biblioteca auxilia na implementação de interfaces que precisam ser modificadas dinamicamente, como por exemplo, com atualizações dependentes da ação do usuário. Em um post anterior do blog, foi apresentada uma introdução ao Knockout.js, que você pode conferir aqui.

Agora, o objetivo é apresentar um exemplo com mais elementos do Knockout. Vamos criar uma página para cadastrar contatos em uma agenda, exibindo os já cadastros na mesma tela. Não iremos implementar a operação de inserção em uma base de dados real, já que o exemplo é meramente ilustrativo.

Para começar, adicionamos o javascript da bilbioteca, como segue.

<script src="scripts/knockout-3.2.0.js"/>

Nosso formulário irá conter três campos: nome, telefone e categoria. Os campos nome e telefone serão obrigatórios. O campo categoria será uma lista de categorias, como família e amigos.

Em seguida, vamos apresentar a estrutura criada na view-model. Inserimos uma lista de categorias, uma estrutura que irá armazenar os dados do novo contato e uma lista de contatos, que será utilizada para exibir na tela os contatos já cadastrados.

function ViewModel() {
      var self = this;

      self.categories = ko.observableArray([ "Família" , "Amigos" ]);

      self.newContact = ko.observable({
                             fullName: "",
			     category: "",
			     phone: ""
			});				

      self.contacts = ko.observableArray([]);
}

Também iremos inserir na view-model um campo para exibir uma mensagem de erro quando apropriado.

self.errorMessage = ko.observable("");

Em seguida, vamos criar o formulário, conforme segue.

<p>Nome Completo:
      <input type="text" data-bind="value: newContact().fullName"> *
</p>

<p>Categoria:
      <select data-bind="options: categories,
                         optionsCaption: 'Escolha...',
                         value: newContact().category">
      </select>
</p>

<p>Telefone:
      <input type="text" data-bind="value: newContact().phone"> *
</p>

Também precisamos inserir o botão para incluir o contato na lista, além da mensagem de erro, que está inicialmente vazia.

<button data-bind="click: addContact">Adicionar</button>

<p style="color: red"><span data-bind='text: errorMessage'/></p>

Na view-model, implementamos o método para inserir o contato da lista, incluindo a validação, que irá preencher a mensagem de erro quando apropriado.

self.addContact = function() {

      if (self.newContact().fullName == "" || self.newContact().phone == "")
            self.errorMessage("Campos marcardos com * são obrigatórios.");
      else
      {
            self.errorMessage("");

            self.contacts.push({
                 fullName: self.newContact().fullName,
 		 category: self.newContact().category,
		 phone: self.newContact().phone
	    });
      }

};

Para exibir os contatos já cadastrados, incluímos na view uma tabela, que será preenchida, através do binding, com uma linha para cada elemento da lista de contatos.

<h3 data-bind='visible: contacts().length > 0'>Meus Contatos</h3>

<table data-bind='visible: contacts().length > 0' border="1">
      <thead>
            <tr>
                  <th>Nome Completo</th>
		  <th>Categoria</th>
		  <th>Telefone</th>
	    </tr>
      </thead>
      <tbody data-bind='foreach: contacts'>
            <tr>
	          <td width="200px"><span data-bind='text: fullName'/></td>
		  <td width="200px"><span data-bind='text: category'/></td>
		  <td width="200px"><span data-bind='text: phone'/></td>
	    </tr>
      </tbody>
</table>

Por fim, aplicamos os bindings ao carregar a página.

<script>
      ko.applyBindings(new ViewModel());
</script>

O resultado é exibido na imagem abaixo, quando clica-se no botão “Adicionar”.

Quando algum campo obrigatório não é preenchido, a mensagem de erro aparece, conforme figura abaixo.

O objetivo desse post foi apresentar novos elementos do Knockout. Novamente podemos perceber os benefícios apresentados no post de introdução, como atualização dinâmica e o fato de não precisarmos de nenhuma outra biblioteca, além do Knockout. Então, o que achou? Qualquer dúvida ou sugestão, deixe nos comentários.

Escrito por Paula Burguêz.

Quem tem medo de JavaScript? Parte 1: É preciso achar o undefined.

Image

Ao longo do tempo em que trabalhamos com desenvolvimento, temos nos deparado com um denominador comum em quase todos projetos: undefined is not a function. Geralmente, JavaScript desperta um enorme pavor em qualquer desenvolvedor, principalmente quando tem que se resolver um problema de maneira rápida num emaranhado de código, que mais parece noruêgues arcaico. O passo inicial para um desenvolvimento mais fácil em JavaScript é uma boa legibilidade de código, mas como fazer isso? Nosso post de hoje tem o objetivo de trazer algumas dicas e exemplos para facilitar a vida de quem enfrenta esse tipo de problema.

Confira abaixo:

Nomenclatura:

- Use nomes sucintos (não abreviados) e que expliquem claramente o objetivo da variável/função.

Opte por uma nomenclatura mais descritiva e de fácil identificação. Abreviações geralmente confundem em códigos que costumam ser mais extensos como os de JavaScript.

- Variáveis em lista devem ser no plural ou devem descrever que são uma lista.

Se colocarmos no singular isto não nos dará uma ideia de lista, e ao percorrer um código modularizado precisamos que ele seja óbvio e evidente pois nem sempre a implementação estará no mesmo arquivo.

Funções:

- Funções geralmente devem conter um verbo.

LEMBRE-SE: Se o nome da função está muito grande provavelmente ela está fazendo demais. SRP (single responsibility principle). “Uma classe deve ter um e somente um motivo para mudar”, transpondo esse princípio para a realidade do JavaScript, significa que uma função deve ter somente uma responsabilidade. Imagine uma função chamada por um botão de registro de usuário como abaixo.

Agora, observe este outro caso:

Esta função evidentemente viola o SRP, pois ela tem três responsabilidades que são: validar, registrar o usuário e efetuar o login. O mais correto seria separar em três funções e encapsular as mesmas  - As funções nunca devem receber mais de dois parâmetros. Caso seja necessário mais dados, devemos encapsular ela em um objeto, como na imagem abaixo.

Para facilitar a legibilidade devemos sempre agrupar as propriedades em objetos comuns. A função acima poderia muito bem receber simplesmente um objeto User com todas as informações necessárias dentro.

Módulos:

Uma das facilidades para desenvolvimento em JavaScript é a divisão por módulos. Dividindo o código por módulos podemos criar objetos e reutilizarmos eles conforme necessário, aumentando assim o reuso do código e tornando o código mais limpo e conciso. Veja o código abaixo. Ele não ficaria mais legível e reaproveitável separado?

Para fazer a modularização do JavaScript existem diversas maneiras e ferramentas. amos conhecer algumas:

UMD (Universal module definition): O UMD é compatível com AMD e com a definição de variável global, permitindo uma compatibilidade maior sem dependências. Particularmente achamos essa maneira uma das piores em termos de organização de código. Ele fica muito ilegível e pouco claro ao contrário do código gerado com RequireJs/CommonJs.

AMD (Asyncronous module definition): O AMD consiste em definir módulos onde tanto o módulo quanto a dependência podem ser carregados assincronamente quando desejado. A sua modularização é voltada muito mais ao browser e uma das suas implementações mais usadas é o RequireJS. Hoje em dia é usado em projetos como o Dojo, jQuery, Firebug.

Common JS: O sucessor do RequireJS é o CommonJS, um sistema de módulos que procurou resolver todos os problemas que os módulos AMD tiveram e tornar a criação e referência modular muito mais fácil. O CommonJS foi criado inicialmente para ser usado apenas em código JavaScript do backend, mas em 2011 o mesmo foi portado para frontend com o nome de Browserify.

ECMAScript 6: Para a alegria de todos, em 2014 foi definida a sintaxe de como o ECMAScript 6 trabalhará com módulos. Particularmente, achamos a maneira mais sucinta e clara para se usar. Contudo, os browsers ainda não dão suporte a modularização e ES6. Uma das formas de se utilizar essa nomenclatura é através do transpilador Babel que torna o código ES6 compatível com ES5, a versão mais atual e estável do ECMAScript e que os principais navegadores suportam.

JSLint: JSLint é uma ferramenta para melhor qualidade de código em JavaScript. Ele verifica o código e dispara erros conforme suas verificações com a localização aproximada do mesmo. Ele ainda permite que as configurações possam ser personalizadas de acordo com a necessidade do usuário, por exemplo, suprimindo exceções e criando filtros para arquivos de terceiros em que não existe a necessidade de verificar por problemas.

Ferramentas:

Existem diversas ferramentas que podem aperfeiçoar o desenvolvimento de JavaScript, tais como transpiladores como o Babel, CoffeScript e TypeScript, Dart, que melhoram muito a simplicidade do código. Tem também frameworks focados primariamente para data binding como KnockoutJS e frameworks MVVM, que tendem a ser mais completos mas em compensação contam uma curva de aprendizagem maior, como AngularJS.

Existem ainda outras ferramentas de desenvolvimento que nos auxiliam no que chamamos de build frontend, para minificar arquivos, comprimir imagens e muito mais. Alguns exemplos são as task-runners como o Grunt, o Gulp e o Broccoli. Acompanhe o blog pois em breve continuaremos esse assunto, analisando as principais ferramentas que podemos usar junto com o JavaScript para tornar o desenvolvimento mais fácil e prático.

Escrito por Andrey Donelli e Fagner Carvalho

Retrospectiva Ágil: O Test-Driven Drawing

No dia 19 de fevereiro promovemos dentro da Mobiltec o Let’s Talk Learning Agile with Games! Pela primeira vez um Let’s Talk, que ocorre toda terceira quinta-feira do mês, contou com uma dinâmica Ágil.

O momento foi muito oportuno: Além de praticar com as equipes conceitos fundamentais dos Métodos Ágeis, de forma muito divertida, aproveitamos a oportunidade para integrar ao time o Riadh Mezzi, nosso intercambista tunisiano que veio passar uma temporada na Mobiltec.

Como desafio adicional, o Let’s Talk foi todo conduzido em inglês, para alívio do Riadh. :)

Desafio: Test-Driven Drawing

A dinâmica utilizada foi o Test-Driven Drawing, relatado no site TastyCupcakes.org, que tem como principal foco a relação entre Product Owner (PO) e Desenvolvedores dentro do ciclo do SCRUM.

A prática realizada mostra exatamente o que se vê nos casos reais de aplicação do SCRUM: Primeiro, por mais precisa que seja a descrição de escopo do PO, os requisitos estão sujeitos a diferentes interpretações por parte do time. Em segundo lugar, a elaboração de testes traz um conjunto de restrições adicionais que guiam o time de desenvolvimento a chegar a melhores resultados.

Levamos aproximadamente 40 minutos para execução da dinâmica, que é organizada em duas iterações.

Equipe trabalhando durante as iterações da dinâmica.

Análise dos resultados!

 

Primeira iteração

A equipe foi toda organizada em duplas (um PO e um desenvolvedor) e um trio (um PO e dois desenvolvedores). PO e desenvolvedor se sentam de costas um para o outro.

O PO ganha uma folha com um conjunto de figuras geométricas, enquanto que o desenvolvedor ganha uma folha branca para desenhar. Na primeira iteração, o PO descreve verbalmente o desenho para o desenvolvedor sem que este possa vê-lo. A partir das instruções, o desenvolvedor deve reproduzir o desenho em seu papel. Esta etapa dura de 3 a 5 minutos.

Um dos resultado da primeira iteração.

 

Segunda iteração

No início da segunda iteração, o PO recebe uma folha em branco onde pode escrever cerca de 5 casos de teste para a segunda iteração.

Nesta etapa cada PO trabalha com um novo desenvolvedor. A dinâmica é similar à da primeira iteração, mas agora o desenvolvedor recebe os casos de teste para guiá-lo enquanto realiza o novo desenho.

Com o apoio dos testes, a segunda iteração ocorrem com melhorias no processo.

Um dos resultados da segunda iteração.

 

Momento Retrospectiva

Ao término da dinâmica o grupo realizou um grande momento de retrospectiva, avaliando as situações ocorridas e comparando-as com situações reais.

Avaliamos a dinâmica do ponto de vista das expectativas do PO e do desenvolvedor. O que POs entendiam que os desenvolvedores poderiam ter feito diferente, para chegar a melhores resultados? E o que os desenvolvedores acham que os POs poderiam ter feito diferente? São questões extremamente pertinentes que nos levam a diversas reflexões, tais como:

  • POs vêem como necessário que os desenvolvedores devem buscar maior compreensão da atividade antes de sair executando.
  • Em contrapartida, desenvolvedores entendem que POs precisam explicar melhor as atividades.
  • Importante que os desenvolvedores prestem atenção constante aos casos de teste.
  • Mas desenvolvedores também questionam a qualidade dos casos de teste.

E quem vê estes problemas ocorrerem na prática, nos seus projetos também? Todos são unânimes em concordar, e acredito que você que nos lê também concorde com estas conclusões.

Indo além, percebemos que todos apontam OS MESMOS PROBLEMAS. O que muda é apenas o ponto de vista. A perspectiva de cada função. No fundo são problemas de comunicação, e ambas partes precisam buscar melhores práticas para aperfeiçoar esta comunicação. A geração de Histórias de Usuário, por exemplo, é uma prática que vale muito a pena (Um ótimo ponto de partida é http://historiasdeusuario.com.br).

A aplicação da técnica foi muito satisfatória! Prova disso é que o debate foi bastante profundo, e o pessoal saiu com várias pautas para as retrospectivas de seus times.

E você, o que achou desta técnica? Deixe seus comentários :)

Escrito por Eduardo Klein

Automatizando o deploy de aplicações ASP.NET com Octopus Deploy

Em um post anterior mostramos como criar e configurar um ambiente de integração contínua com Jenkis e Git. Neste tutorial faremos o deploy de nossa aplicação ASP.NET e, para isso, utilizaremos a ferramenta de automatização de deploy Octopus Deploy.

O Octopus Deploy permite a realização do deploy de aplicações ASP.NET (Web Forms, MVC e Web API) automatizando uma série de procedimentos durante este processo:

  • Atualização de arquivos de configuração: faz as devidas transformações nos arquivos .config de acordo com o ambiente (homologação, produção, etc).
  • Atualização do IIS: Automaticamente atualiza o website ou application pool do IIS.
  • Execução de scripts powershell: Possibilita que sejam incluídos scripts powershell para execução em diversos estágios do deploy (ex: um script para reinicialização de um serviço windows logo após o deploy). Não utilizaremos nenhum script post deploy neste exemplo.

Antes de começar a configuração do Octopus, vamos rever todo o fluxo que estamos criando:

Instalando o Octopus Server e Configurando o Nuget Feed

O Octopus consiste em dois módulos principais: Octopus Server e Octopus Tentacle. O Octopus Server é utilizado para o gerenciamento de todos os projetos e é onde iremos definir o Nuget Feed (diretório que contém os pacotes Nuget que devem ser descarregados no servidor-alvo). Os arquivos para instalação podem ser obtidos na página de downloads do Octopus.

  1. Instale o Octopus Server: Requer Windows Server 2008 ou superior. A instalação é intuitiva e nela você irá definir a URL para acesso ao painel administrativo e o usuário administrador. Você precisará do thumbprint gerado para identificar esta instância do Octopus Server no Octopus Tentacle (Configuration > Certifications).
  1. Configure o Nuget Feed: O Nuget Feed indica ao Octopus Server qual é o diretório do seu repositório de pacotes Nuget (que contém o conteúdo a ser descarregado no servidor web). No painel administrativo do Octopus Server, clique no menu superior direito em Configuration e, no menu esquerdo, em Nuget. Em seguida, clique em Add Nuget Feed e informe o caminho do diretório (ex: C:\Octopus\NugFeed\MeuProjeto).

Configurando Environment, Machine e Role

Antes de começar a configuração, precisamos conhecer estes três conceitos relacionados à forma como o Octopus Server gerencia os seus projetos.

  • Environment: O Octopus permite a criação de diversos environments – um para cada projeto a ser feito deploy. Cada um destes environments contém um conjunto de máquinas nas quais o deploy deverá ser feito. Você poderia ter em um mesmo environment duas máquinas – um servidor de homologação e um servidor de produção – por exemplo.
  • Machine: Cada máquina do ambiente deverá ter uma instância do Octopus Tentacle rodando (veremos mais adiante neste tutorial).
  • Role: Suponha que um ambiente possua duas máquinas. Como saber qual delas é a que será utilizada para homologação e qual será a de produção? Esta é a tarefa dos roles.

Vamos então criar um novo environment. Mais adiante iremos adicionar a máquina-alvo e configurar seu role.

  1. Adicionar um environment: Ainda no painel de controle do Octopus Server , clique em Environments, Add Environment e informe o nome e a descrição do novo ambiente.

Instalando o Octopus Tentacle

O Octopus Tentacle é um serviço windows que deve ser instalado no servidor onde você pretende fazer o deploy. Este serviço deve estar permanentemente sendo executado pois é ele quem recebe os comandos do Octopus Server e faz o deploy propriamente (assim como as demais tarefas como atualização do IIS e execução de scripts).

  1. Instale o Octopus Tentacle: No servidor onde você pretende fazer o deploy execute o instalador do tentáculo. Novamente, o instalador é bem simples e intuitivo. Assim que o processo for concluído será aberto o Tentacle Manager.

  1. Configurar o Octopus Tentacle: Assim que você clicar em Get Started será necessário completar uma série de etapas. Na figura abaixo vemos a etapa Listening Tentacle onde você pode indicar em qual porta o Octopus Tentacle espera receber as instruções do Octopus Server. É nesta etapa que deve ser informado o Octopus Server Thumbprint que anotamos anteriormente. Desta forma o Octopus Tentacle poderá identificar o Octopus Server quando receber instruções. Depois disso, basta finalizar a instalação.

  1. Anotar o thumbprint do tentáculo: Após a instalação terminar você verá a tela de administração. Nela você deve localizar e anotar o thumbprint gerado.

Adicionar uma nova máquina ao ambiente

Agora que o tentáculo está instalado e rodando no servidor temos de voltar ao gerenciador de environments do Octopus Server e adicionar uma nova máquina ao environment Exemplo.

  1. Adionar uma máquina ao ambiente: No painel de controle, clique em Environments e então em Add Machine para abrir a tela abaixo. Informe um nome para o servidor, a URL (a porta deve ser a mesma que foi configurada no tentáculo). Informe o thumbprint que anotamos durante a instalação do tentáculo. Observe que criamos um role chamado producao para esta máquina (basta escrever o nome do role e pressionar Enter).
  1. Check Health: Para verificar se o Octopus Server consegue se comunicar corretamente com o Octopus Tentacle, clique (no topo da tela de environments) no botão check health. Caso ocorra algum erro verifique as permissões de firewall do serviço Octopus Tentacle e se ambos os thumbprints foram informados corretamente.

Criar um novo projeto no Octopus Server

Vamos revisar o que temos até agora:

  • O Octopus Server está instalado e com o repositório de pacotes configurado (Nuget Feed).
  • O Octopus Tentacle está instalado no servidor-alvo e está se comunicando corretamente com o Octopus Server.
  • No Octopus Server criamos um environment que contém uma máquina. Esta máquina foi configurada com o thumbprint gerado no seu Octopus Tentacle.
Criaremos agora, no Octopus Server, um novo projeto. Um projeto consiste nos seguintes componentes:

  • Steps: O processo de deploy pode ser subdivido em diversas etapas. Você pode restringir um step para que atue em um determinado environment e ainda criar dependências entre os steps.
  • Variables: São configurações específicas do projeto e da máquina onde pretendemos fazer o deploy (ex: o nome do website no servidor IIS-alvo). Mais informações sobre variáveis podem ser obtidas aqui.
  • Releases: Uma release é o resultado da execução de todos os steps do projeto. O histórico de releases pode ser visto na aba Overview do projeto o que facilita muito quando se precisa retornar rapidamente para um release anterior.
  1. Criar um novo project group: Na tela administrativa do Octopus Server, clique em Projects e em seguida em Add project group. Não esqueça de informar o Environment criado anteriormente.

  1. Criar um novo projeto: Com o project group criado, clique em Add Project para visualizar a tela de criação de um novo projeto. Informe o nome e a descrição e clique em Add.
  1. Criar um novo step: Com o projeto criado, clique nele e em seguida na aba Steps. Clique em Add Step para adicionar. Na lista dropdown da tela seguinte, selecione a opção Deploy a Nuget Package.
  1. Configurar o step: Na tela de configuração informe o nome do step, o repositório de pacotes Nuget (criado anteriormente) e o nome do pacote que o Octopus deve utilizar (ele ainda não existe e será criado posteriormente – neste exemplo, chamei de MinhaAppMVC). Veja também que em Deploy to roles especificamos a role da máquina onde instalamos o Octopus Tentacle. As demais configurações não devem ser modificadas.
  1. Configurar as variáveis do projeto: Nosso projeto precisa apenas de uma variável chamada OctopusWebSiteName. Ela serve para informar ao Octopus qual o nome do web site no servidor IIS que desejamos que este projeto esteja vinculado. No exemplo o website foi criado com o nome Minha App. Veja que especificamos os campos Environment, Role, Machine e Step conforme o que foi criado anteriormente.

De volta ao Jenkins – criar pacote Nuget e executar o deploy

O Octopus Server está pronto para criar novas releases para o nosso projeto. Iremos agora criar um novo job no Jenkins e utilizar um script MSBuild para criar um novo pacote Nuget contendo o resultado do build feito anteriormente (ver este post para criar o job no Jenkins). Em seguida, o script fará uma chamada à API do Octopus para criar a release.

Antes de continuar, será necessário:

  • Fazer o download o Nuget.exe aqui. Ele será utilizado para compactar seu projeto em um .nupkg.
  • Criar um arquivo nuspec no diretório que pretende compactar (raiz da solução). O arquivo Nuspec serve apenas para conter metadados sobre o pacote (versão, autor, etc). Este arquivo deve ter o mesmo nome que foi informado no campo Nuget Package do step do projeto – no nosso caso, MinhaAppMVC.nuspec. Você pode encontrar exemplos de arquivos Nuspec aqui.
  • Obter a extensão do MSBuild MSBuildTasks aqui. Esta extensão permitirá que o MSBuild atualize o arquivo Nuspec a cada novo empacotamento.
  1. Criar novo job no Jenkins: Crie um novo job chamado Meu Projeto – Empacotamento e Deploy. Especifique dois scripts na seção Build – empacotamento.build e deploy.build.
  1. Criar novo script MSBuild chamado empacotamento.build: Este script cria um pacote Nuget (extensão .nupkg) do projeto previamente compilado. Observe as seguintes variáveis no script:
  • ProjectNuspec: caminho para o arquivo Nuspec da solução.
  • OctopusNugetFeed: o pacote Nuget resultante deve ficar no Nuget feed configurado no Octopus anteriormente.
  • Nuget: O caminho do executável Nuget.exe.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTargets="Start">
  <Import Project="$(MSBuildExtensionsPath)MSBuildCommunityTasksMSBuild.Community.Tasks.Targets"/>

  <PropertyGroup>
    <ProjectNuspec>C:\MinhaApp\MinhaAppMVC.nuspec</ProjectNuspec>
    <OctopusNugetFeed>C:\OctopusNugFeed\MeuProjeto</OctopusNugetFeed>
    <Nuget>C:\Mobiltracker\Nuget\NuGet.exe</Nuget>
  </PropertyGroup>

  <!-- Target principal - executa demais targets na ordem correta -->
  <Target Name="Start">
    <CallTarget Targets="Init"/>
    <CallTarget Targets="Pack"/>
  </Target>

  <!-- Cria o repositorio de pacotes caso ainda nao exista -->
  <Target Name="Init">
    <MakeDir Directories="$(OctopusNugetFeed)" />
  </Target>

  <!-- Atualiza a versao no nuspec, compacta o projeto e envia para a pasta destino -->
  <Target Name="Pack">
    <XmlUpdate
		  Namespace="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"
		  XmlFileName="$(ProjectNuspec)"
		  XPath="/package/metadata/version"
		  Value="$(VersionNumber)" />

    <Exec Command="$(Nuget) pack $(ProjectNuspec) -OutputDirectory $(OctopusNugetFeed)" />
  </Target>
</Project>

Veja que atualizamos o arquivo Nuspec com a task XmlUpdate. Na propriedade Value utilizamos uma variável chamada $(VersionNumber) que você não irá encontrar declarada no script. Isto porque trata-se de uma variável de ambiente criada pelo Jenkins e acessível no script (veja abaixo onde ela foi criada na configuração do job).

No campo Version Number Format String utilizamos uma máscara que indica que o valor da variável será gerado dinamicamente a partir da data de criação do projeto (especificado em Project Start Date). Assim, teremos versões do tipo 3.1.20.1 (3 anos, 1 mês, 20 dias, 1º release do dia).

  1. Criar uma nova API Key no painel de controle do Octopus: Faremos uma chamada à API do Octopus a partir do script MSBuild. Para isso, será necessário informar a chave da API que pode ser obtida (no painel do Octopus Server) em My Profile > Generate New API Key

  1. Obter o Octo.exe (Command Line). Esta ferramenta permite que sejam executadas comandos a partir do script. Ela pode ser obtida aqui .
  1. Criar novo script MSBuild chamado deploy.build: Este script é responsável por fazer uma chamada para a API do Octopus para a criação de uma nova release (a partir do pacote gerado no script anterior). Observe as seguintes variáveis no script:
  • OctopusProject: Nome do projeto criado no Octopus Server.
  • Environment: Ambiente criado no Octopus Server que contém a máquina-destino.
  • OctopusNugetFeed: Caminho do repositório de pacotes nuget (onde já deve estar o pacote gerado pelo script empacotamento.build).
  • OctopusAPIEndPoint: URL do Octopus Server
  • OctopusAPIKey: chave gerada no painel do Octopus Server
  • Octopus: Caminho do executável Octo.exe
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTargets="OctopusDeployToDev">
  <Import Project="$(MSBuildExtensionsPath)MSBuildCommunityTasksMSBuild.Community.Tasks.Targets"/>

  <!-- Propriedades globais -->
  <PropertyGroup>
    <OctopusProject>Exemplo</OctopusProject>
    <Environment>Exemplo</Environment
    <OctopusNugetFeed>C:\Octopus\NugFeed\MeuProjeto</OctopusNugetFeed>
    <OctopusAPIEndPoint>http://octopusserver:9090/api</OctopusAPIEndPoint>
    <OctopusAPIKey>XXX</OctopusAPIKey>
    <Octopus>C:\Jenkins\tools\Octo\Octo.exe</Octopus>
  </PropertyGroup>

  <!-- Executa API do Octopus para fazer deploy em ambiente de homologacao -->
  <Target Name="OctopusDeployToDev">
    <Exec Command="$(Octopus) create-release --project=&quot;$(OctopusProject)&quot; --version=$(VersionNumber) --server=$(OctopusAPIEndPoint) --apiKey=$(OctopusAPIKey)" />
    <Exec Command="$(Octopus) deploy-release --project=&quot;$(OctopusProject)&quot; --releaseNumber=$(VersionNumber) --deployto=&quot;$(Environment)&quot; --server=$(OctopusAPIEndPoint) --apiKey=$(OctopusAPIKey) --forcepackagedownload=true --force=true --waitfordeployment" />
  </Target>
</Project>

Se o job do Jenkins foi concluído sem erros um novo release foi gerado pelo Octopus e pode ser visualizado nos detalhes do projeto. Na saída do console do job você verá algo como:

  Finding project: Exemplo
  Handshaking with Octopus server: http://&lt;&lt; IP DO OCTOPUS SERVER &gt;&gt;:9090/api
  Handshake successful. Octopus version: 1.6.1.1718; API version: 2.0.0
  Finding environments...
  Finding steps for project...
  Resolving NuGet package versions...
    - Finding latest NuGet package for step: Deploy
  Release plan for release:    3.2.19.1
  Steps:
    #   Name      Version         Source
    --- --------- --------------- ------------------------------------
    1   Deploy    3.2.19.1        Latest available in NuGet repository

  Creating release...
  Release created successfully!
  C:\Jenkins\tools\Octo\Octo.exe deploy-release --project="Exemplo" --releaseNumber=3.2.19.1 --deployto="Exemplo" --server=http://&lt;&lt; IP DO OCTOPUS SERVER &gt;&gt;:9090/api --apiKey=XXX--forcepackagedownload=true --force=true --waitfordeployment
  Octopus Command Line Tool, version 1.1.17.62

  Finding project: Exemplo
  Handshaking with Octopus server: http://&lt;&lt; IP DO OCTOPUS SERVER &gt;&gt;/api
  Handshake successful. Octopus version: 1.6.1.1718; API version: 2.0.0
  Finding environments...
  Finding release: 3.2.19.1
  Successfully scheduled release 3.2.19.1 for deployment to environment Exemplo

Conclusão

Com os tutoriais mostrados neste post e em Integração Contínua de Projeto .NET com Jenkins você pode criar um sistema de continuous delivery simplificado mas pronto para melhoramentos como:

  • Criação de um pipeline no Jenkins que impeça o deploy caso os testes não passem.
  • Incluir o job de empacotamento e deploy no pipeline de jobs.
  • Criação de avisos por e-mail à equipe de desenvolvimento.
  • Condicionar o deploy à aprovação de um gerente.
  • Geração de relatórios.

Escrito por Rafael Companhoni.