.NET CORE - Como criar um executável e publicar um pacote independente

O objetivo desse artigo é detalhar como criar uma aplicação .NET Core Console através da ferramenta de linha de comando (.NET CLI - DOTNET Command Line Interface) e do Visual Studio Code.
 
O primeiro passo é baixar o SDK do .NET Core, disponível para download em: .NET SDK. Observe que é possível realizar o download para várias plataformas, por exemplo:
 

  • Windows
  • Linux (RHEL, Ubuntu, Linux Mint, Debian, Fedora, CentOS, Oracle Linux e openSUSE)
  • Mac
  • Docker

 
O .NET Core SDK fornece um conjunto de bibliotecas e ferramentas para criar aplicações e bibliotecas .NET Core. Após a instalação, por padrão, o .NET SDK está localizado em C:\Program Files\dotnet\SDK. Observe que lá existem todas as versões instaladas do .NET Core (side-by-side):
 
dotnetSDK

 

modularO .NET Core é baseado em um conjunto de pacotes NuGet que permite otimizar as aplicações para ser dependente apenas dos módulos necessários. Isso reduz o tempo necessário para testar e implantar atualizações que não são usadas pela aplicação.
 
Como as funcionalidades do .NET Framework estão agrupadas em pacotes NuGet, o tempo de atualização e entrega desses pacotes é muito menor. Não é necessário aguardar uma atualização do framework como um todo.
 
Os pacotes NuGet, instalados pelo .NET Core SDK, ficam disponíveis para uso no diretório .nuget\packages do perfil do usuário:
 
nugetpackages

 

Para criar uma nova aplicação, via linha de comando, abra o prompt de comando (ou PowerShell) e crie um diretório:
 

mkdir C:\temp\HelloWorldConsole

cd .\Temp\HelloWorldConsole

 
O .NET Core SDK fornece a ferramenta de linha de comando (.NET Core CLI) através do executável dotnet. Para listar os comandos disponíveis para a ferramenta, digite:
 

 dotnet -h

 
Observe que existem, por padrão, os seguintes comandos:
 
clicomoncommmands
 
Para criar uma nova aplicação do tipo Console é necessário utilizar o comando new. Para listar os tipos de projetos que o comando oferece, digite o seguinte comando:
 

dotnet new -h

 
Observe que o comando new oferece a opção de escolhe a linguagem (C# ou F#), através do parâmetro -l, e o tipo de projeto, através do parâmetro -t, conforme:
 
newoptions
 
Os tipos de projetos que o .NET CLI disponibiliza são:
 

  • Console
  • Web
  • Lib
  • xunittest

 
Para criar uma nova aplicação console, digite o seguinte comando:
 

dotnet new -t console

 
Os seguintes arquivos serão criados:
 

  • Program.cs
  • project.json

 
O arquivo Program.cs contém o código-fonte da aplicação:
 

 

namespace ConsoleApplication
{
    public class Program
    {
        public static void Main (string [] args)
        {
            Console.WriteLine ( "Hello World!");
        }
    }
}

 
Observe que o código é a mesma a de uma aplicação C# tradicional. Já o arquivo project.json contém todas as informações de compilação, dependências e runtimes utilizadas pela aplicação:
 

 
{
    "Version", "1.0.0- *"
    "BuildOptions": {
        "DEBUGTYPE": "portable", // reports will be generated if debugging information
        "EmitEntryPoint": true // tells whether the module contains an entry for the MAIN method
    },
    "Dependencies": {}, // informs the project dependencies
    "Frameworks": {
        "Netcoreapp1.0": {// tells you what is the application framework
            "Dependencies": {
                "Microsoft.NETCore.App": {// use the super set that contains the .NET Standard Library and standard library
                    "Type": "platform", // tells the machine framework will be used
                    "Version", "1.0.1"
                }
            },
            "Imports": "dnxcore50"
        }
    }
}

 
O próximo passo é restaurar as dependências, através do comando a seguir, para baixar as bibliotecas NUGET da Internet para o diretório padrão de cache das bibliotecas NUGET (%USERPROFILE%\.nuget\packages\ ou $env:UserProfile\.nuget\packages\). Caso não existir conexão com a internet, o diretório de CACHE será utilizado.
 

dotnet restore

 
Após restaurar as bibliotecas, o arquivo project.lock.json é gerado com as referências da aplicação:
 

 
{
    "Locked": false,
    "Version": 2
    "Targets": {
        ".NETCoreApp, Version = v1.0": {
        "Libuv / 1.9.0": {
            "Type": "package"
            "Dependencies": {
                "Microsoft.NETCore.Platforms": "1.0.1"
            },
            "RuntimeTargets": {
                "Runtimes / osx / native /_._": {
                    "AssetType": "native"
                    "Rid": "osx"
                }
            }
        },
        "Microsoft.CodeAnalysis.Analyzers / 1.1.0": {
            "Type": "package"
        },
        "Microsoft.CodeAnalysis.Common / 1.3.0": {
            "Type": "package"
            "Dependencies": {
                "Microsoft.CodeAnalysis.Analyzers" "1.1.0"
                "System.AppContext" "4.1.0"
                "System.Collections": "4.0.11"
                "System.Collections.Concurrent": "4.0.12"
                "System.Collections.Immutable" "1.2.0"
                // ... And several other references omitted here because of the amount

 
Para compilar a aplicação utilize o comando:
 

dotnet build

 
Por padrão, esse comando irá compilar a aplicação em modo DEBUG. Para compilar em modo RELEASE, utilize o parâmetro:
 

dotnet build -c release

 
A aplicação será compilada no diretório a seguir, onde o netcoreapp1.0 representa o framework padrão selecionado (.NET Core):
 

.\bin\release\netcoreapp1.0\HelloWorldConsole.dll

 
Para adicionar o FULL .NET Framework (tradicional que faz parte do sistema operacional), adicione a referência "net461", conforme:

 

 
{
    "Version", "1.0.0- *"
    "BuildOptions": {
        "DEBUGTYPE": "portable", // reports will be generated if debugging information
        "EmitEntryPoint": true // tells whether the module contains an entry for the MAIN method
    },
    "Dependencies": {}, // informs the project dependencies
    "Frameworks": {
         "Net461": {}, 
        "Netcoreapp1.0": {// tells you what is the application framework
            "Dependencies": {
                "Microsoft.NETCore.App": {// use the superset that contains the .NET Standard Library and standard library
                    "Type": "platform", // tells the machine framework will be used
                    "Version", "1.0.1"
                }
            },
            "Imports": "dnxcore50"
        }
    }
}

 
Ao listar o diretório C:\Temp\HelloWorldConsole\bin\release, observe que existem as seguintes pastas para cada versão do .NET Framework:
 

  • net461
  • netcoreapp1.0

 
Para executar o projeto, digite o seguinte comando:
 

dotnet run

 
Para publicar a aplicação, ou seja, empacotar a aplicação e suas dependências em uma pasta, digite o seguinte comando:
 

dotnet publish -c release

 
Como eu defini dois frameworks (.NET Core e Full .NET Framework) no project.json, serão publicados dois diretórios para os respectivos frameworks:
 

  • C:\Temp\HelloWorldConsole\bin\release\netcoreapp1.0\publish
  • C:\Temp\HelloWorldConsole\bin\release\net461\win7-x64\publish

 
Observe que no diretório do .NET Framework Full (tradicional - C:\Temp\HelloWorldConsole\bin\release\net461\win7-x64\publish), é gerado o seguinte executável:
 

HelloWorldConsole.exe

 
Já o mesmo não ocorre para o diretório do .NET Core (C:\Temp\HelloWorldConsole\bin\release\netcoreapp1.0\publish), onde é gerada a DLL:

HelloWorldConsole.dll

 
Nesse caso, para que a aplicação seja executada, é necessário utilizar o comando:

dotnet HelloWorldConsole.dll

 
Isso pode ser explicado através do arquivo project.json, onde o elemento type, da dependência do .NET Core (netcoreapp1.0), está configurado para utilizar o framework da máquina através do elemento "type": "platform" :
 

 
"Netcoreapp1.0": {// tells you what is the application framework
    "Dependencies": {
    "Microsoft.NETCore.App": {// use the superset that contains the .NET Standard Library and standard library
         "Type": "platform" , // tells the machine framework will be used
        "Version", "1.0.1"
    }
},

 
Para que seja gerado um executável ao invés de uma DLL, de forma que a aplicação seja independente (self-contained) do framework instalado na máquina, é necessário remover o elemento "type": "platform" :
 

 
    "Netcoreapp1.0": {// tells you what is the application framework
    "Dependencies": {
        "Microsoft.NETCore.App": {// use the superset that contains the .NET Standard Library and standard library
        "Version", "1.0.1"
    }
},

 
Nesse caso, também é necessário adicionar quais serão os runtimes que a aplicação deverá ser compilada, através da tab runtimes. Exemplo para compilar para o Windows e Ubuntu:
 

 
{
    "Version", "1.0.0- *"
    "BuildOptions": {
        "DEBUGTYPE": "portable", // reports will be generated if debugging information
        "EmitEntryPoint": true // tells whether the module contains an entry for the MAIN method
    },
    "Dependencies": {}, // informs the project dependencies
    "Frameworks": {
        "Net461": {},
        "Netcoreapp1.0": {// tells you what is the application framework
            "Dependencies": {
                "Microsoft.NETCore.App": {// use the superset that contains the .NET Standard Library and standard library
                    "Version", "1.0.1"
                }
            },
            "Imports": "dnxcore50"
        }
    },
     "Runtimes": {  
     "Win7-x64": {},  
     "Ubuntu.14.04-x64": {}  
     }  
}

 
Após salvar a alteração, é necessário restaurar as dependências do projeto:
 

dotnet restore

 
Para publicar a aplicação, execute o seguinte comando:
 

dotnet publish -c release

 
Observe que agora o executável foi gerado, assim como, todas as dependências da aplicação (incluindo as DLLs do framework) foram adicionadas na pasta publish:
 
selfcontained
 
Para reduzir o número de dependências, nós podemos alterar o framework para o "netstandard1.6" que é um subconjunto do netcoreapp1.0(que inclui o NET Core CLR e o .NET Core Library). O framework e as dependências ficariam da seguinte forma:
 

 
{
    "version": "1.0.0-*",
    "buildOptions": {
        "debugType": "portable",
        "emitEntryPoint": true
    },
    "dependencies": {
        "NETStandard.Library": "1.6.0",
        "Microsoft.NETCore.Runtime.CoreCLR": "1.0.2",
        "Microsoft.NETCore.DotNetHostPolicy":  "1.0.1"
    },
    "frameworks": {
        "netstandard1.6": { }
    },
    "runtimes": {
        "win10-x64": {},
        "osx.10.10-x64": {}
    }
}

 

Observe que a quantidade de DLLs foi reduzida para:
 
netstandard
 
Por se tratar de uma aplicação Hello World, é possível reduzir ainda mais a quantidade de dependências:

 

 
{
    "version": "1.0.0-*",
    "buildOptions": {
        "debugType": "portable",
        "emitEntryPoint": true
    },
    "dependencies": {
        "System.Console": "4.0.0",
        "Microsoft.NETCore.Runtime.CoreCLR": "1.0.2",
        "Microsoft.NETCore.DotNetHostPolicy":  "1.0.1"
    },
    "frameworks": {
    "netstandard1.6": { }
    },
    "runtimes": {
        "win10-x64": {},
        "osx.10.10-x64": {}
    }
}

 
Observe que a quantidade de DLLs foi reduzida para:
 
system
 
Espero que tenham gostado.
 
Referências: