Setembro de 2017

Volume 32 - Número 9

.NET Standard - Desmistificando o .NET Core e o .NET Standard

Por Immo Landwerth | Setembro de 2017

Como os mais novos membros da família .NET, há muita confusão sobre .NET Core e .NET Standard e como eles diferem do .NET Framework. Neste artigo, explicarei exatamente o cada um deles e examinar quando escolher cada um.

Antes de entrar em detalhes, é útil examinar a imagem maior do .NET para ver onde o .NET Core e o .NET Standard se encaixam. Quando o .NET Framework foi lançado pela primeira vez há 15 anos, ele tinha uma única pilha .NET que você poderia usar para compilar aplicativos Web e da área de trabalho do Windows. Desde então, outras implementações do .NET surgiram, como o Xamarin, que você pode usar para compilar aplicativos móveis para iOS e Android, bem como aplicativos de área de trabalho do MacOS, como mostrado na Figura 1.

O cenário .NET

Figura 1 O cenário .NET

Esta é a forma como o .NET Core e o .NET Standard se encaixam nisso:

  • .NET Core: esta é a última implementação do .NET. Ele é um software livre e está disponível para vários sistemas operacionais. Com o .NET Core, você pode compilar aplicativos de console de plataforma cruzada e aplicativos Web do ASP.NET Core e serviços de nuvem.
  • .NET Standard: este é o conjunto de APIs fundamentais (geralmente conhecidas como biblioteca de classes base ou BCL) que todas as implementações do .NET devem implementar. Tendo como alvo o .NET Standard, você pode compilar bibliotecas que poderão ser compartilhadas em todos os seus aplicativos .NET, qualquer que seja a implementação do .NET ou sistema operacional em que elas sejam executadas.

Introdução ao .NET Core

O .NET Core é uma nova implementação do .NET de plataforma cruzada e de software totalmente livre que foi derivado do .NET Framework e do Silverlight. Ele é otimizado para cargas de trabalho móveis e de servidor, permitindo implantações XCOPY autocontidas.

Para obter uma melhor noção do .NET Core, vamos examinar em mais detalhes o desenvolvimento do .NET Core. E vamos fazer isso explorando as novas ferramentas baseadas em linha de comando. Você também pode usar o Visual Studio 2017 para o desenvolvimento do .NET Core, mas, se estiver lendo este artigo, é provável que você já conheça o Visual Studio, então, vou me concentrar na nova experiência.

Quando o .NET foi criado, ele foi bastante otimizado para o desenvolvimento rápido de aplicativos no Windows. Na prática, isso significa que o desenvolvimento do .NET e o Visual Studio eram amigos inseparáveis. E uma coisa é certa: usar o Visual Studio para desenvolvimento é fenomenal. Ele é super produtivo e o depurador é o melhor que já usei.

No entanto, há casos em que o uso do Visual Studio não é a opção mais conveniente. Digamos que você queira apenas testar o .NET para aprender C#: você não deve baixar e instalar um IDE de vários gigabytes. Ou, digamos que você esteja acessando uma máquina Linux em SSH, onde usar um IDE simplesmente não seja uma opção. Ou, digamos que você seja simplesmente alguém que prefere usar uma interface de linha de comando (CLI).

É por isso que foi criada uma CLI de primeira classe, denominada CLI do .NET Core. O driver principal da CLI do .NET Core é chamado "dotnet". Você pode usá-lo para praticamente todos os aspectos do seu desenvolvimento, incluindo criação, compilação, teste e projetos de empacotamento. Vamos ver como deve ser isso.

Comece criando e executando um aplicativo de console Olá, Mundo (uso o PowerShell no Windows, mas isso funcionará muito bem também com o Bash no macOS ou no Linux):

$ dotnet new console -o hello
$ cd hello
$ dotnet run
Hello World!

O comando "dotnet new" é o equivalente CLI de Arquivo | Novo Projeto no Visual Studio. Você pode criar uma variedade de diferentes tipos de projetos. Digite "dotnet new" para ver os diferentes modelos pré-instalados.

Agora, vamos extrair parte da lógica para uma biblioteca de classes. Para isso, primeiro crie um projeto de biblioteca de classes que seja paralelo ao seu projeto de Olá:

$ cd ..
$ dotnet new library -o logic
$ cd logic

A lógica que você deseja encapsular é a construção de uma mensagem Olá, Mundo, portanto, altere o conteúdo de Class1.cs para o seguinte código:

namespace logic
{
  public static class HelloWorld
  {
      public static string GetMessage(string name) => $"Hello {name}!";
  }
}

Neste ponto, você também deve renomear Class1.cs para HelloWorld.cs:

$ mv Class1.cs HelloWorld.cs

Observe que você não precisa atualizar o arquivo de projeto para essa alteração. Os novos arquivos de projeto usados ​​no .NET Core simplesmente incluem todos os arquivos de origem do diretório do projeto. Assim, para adicionar, remover e renomear arquivos, não é mais necessário modificar o projeto. Isso torna as operações dos arquivos mais fáceis a partir da linha de comando.

Para usar a classe HelloWorld, você precisa atualizar o aplicativo hello para fazer referência à biblioteca lógica. Para isso, você pode editar o arquivo de projeto ou usar o comando dotnet add reference:

$ cd ../hello
$ dotnet add reference ../logic/logic.csproj

Agora, atualize o arquivo Program.cs para usar a classe HelloWorld, como mostrado na Figura 2.

Figura 2 Atualizando o arquivo Program.cs para usar a classe HelloWorld

using System;
using logic;
namespace hello
{
class Program
{
static void Main(string[] args)
{
Console.Write("What's your name: ");
var name = Console.ReadLine();
var message = HelloWorld.GetMessage(name);
Console.WriteLine(message);
}
}
}

Para compilar e executar seu aplicativo, basta digitar dotnet run:

$ dotnet run
What's your name: Immo
Hello Immo!

Você também pode criar testes a partir da linha de comando. A CLI suporta MSTest, bem como a estrutura popular xUnit. Vamos usar xUnit neste exemplo:

$ cd ..
$ dotnet new xunit -o tests
$ cd tests
$ dotnet add reference ../logic/logic.csproj

Altere o conteúdo do UnitTest1.cs, conforme mostrado na Figura 3, para adicionar um teste.

Figura 3 Alterando o conteúdo de UnitTest1.cs para adicionar um teste

using System;
using Xunit;
using logic;
namespace tests
{
public class UnitTest1
{
[Fact]
public void Test1()
{
var expectedMessage = "Hello Immo!";
var actualMessage = HelloWorld.GetMessage("Immo");
Assert.Equal(expectedMessage, actualMessage);
}
}
}

Agora você pode executar os testes invocando o teste dotnet:

$ dotnet test
Total tests: 1. Passed: 1. Failed: 0. Skipped: 0.
Test Run Successful.

Para tornar isso um pouco mais interessante, vamos criar um site simples do ASP.NET Core:

$ cd ..
$ dotnet new web -o web
$ cd web
$ dotnet add reference ../logic/logic.csproj

Edite o arquivo Startup.cs e altere a invocação de app.Run para usar a classe HelloWorld deste modo:

app.Run(async (context) =>
{
  var name = Environment.UserName;
  var message = logic.HelloWorld.GetMessage(name);
  await context.Response.WriteAsync(message);
});

Para iniciar o servidor Web de desenvolvimento, basta usar o dotnet novamente:

$ dotnet run
Hosting environment: Production
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.

Procure a URL exibida, que deve ser http://localhost:5000.

Neste ponto, a estrutura do seu projeto deve ser semelhante à da Figura 4.

Figura 4 A estrutura de projeto criada

$ tree /f
│
├───hello
│ hello.csproj
│ Program.cs
│
├───logic
│ HelloWorld.cs
│ logic.csproj
│
├───tests
│ tests.csproj
│ UnitTest1.cs
│
└───web
Program.cs
Startup.cs
web.csproj

Para facilitar a edição dos arquivos usando o Visual Studio, vamos criar também um arquivo de solução e adicionar todos os projetos à solução:

$ cd ..
$ dotnet new sln -n HelloWorld
$ ls -fi *.csproj -rec | % { dotnet sln add $_.FullName }

Como você pode ver, a CLI do .NET Core é eficiente e resulta em uma experiência básica que os desenvolvedores de outros ambientes talvez achem bastante familiar. E, quando você usar dotnet com o PowerShell no Windows, a experiência será bem semelhante à do Linux ou do MacOS.

Outro grande benefício do .NET Core é que ele suporta implantações autocontidas. Você poderia colocar seu aplicativo em contêiner usando o Docker de tal forma que ele tenha sua própria cópia do tempo de execução do .NET Core. Isso permite que você execute diferentes aplicativos na mesma máquina usando diferentes versões do .NET Core sem que elas interfiram entre si. Como o .NET Core é de software livre, você também pode incluir compilações noturnas ou até mesmo versões que modificou ou compilou, incluindo possivelmente as modificações feitas. No entanto, isso está além do escopo deste artigo.

Introdução ao .NET Standard

Quando você compila experiências modernas, seu aplicativo geralmente abrange vários fatores forma e, portanto, diversas implementações do .NET. Neste momento, os clientes esperam muito poderem usar seu aplicativo Web a partir do celular e que os dados possam ser compartilhados através de um back-end baseado em nuvem. Ao usar um laptop, eles também querem obter acesso através de um site da Web. E para a sua própria infraestrutura, você provavelmente desejará usar ferramentas de linha de comando e até mesmo aplicativos de área de trabalho para permitir que sua equipe gerencie o sistema. Consulte a Figura 5 para saber como as diferentes implementações do .NET funcionam dessa forma.

Figura 5 Descrições das implementações do .NET

  OS Software Livre Propósito
.NET Framework Windows No Usado para compilar aplicativos de área de trabalho do Windows e aplicativos Web do ASP.NET em execução no IIS.
.NET Core Windows, Linux, macOS Sim Usado para compilar aplicativos de console de plataforma cruzada e aplicativos Web do ASP.NET Core e serviços de nuvem.
Xamarin iOS, Android, macOS Sim Usado para compilar aplicativos móveis para iOS e Android, bem como aplicativos de área de trabalho para macOS.
.NET Standard N/D Sim Usado para compilar bibliotecas que podem ser referenciadas de todas as implementações do .NET, como o .NET Framework, o .NET Core e o Xamarin.

Nesse tipo de ambiente, o compartilhamento de código torna-se um grande desafio. Você precisa entender onde as APIs estão disponíveis e verificar se os componentes compartilhados apenas usam APIs que estão disponíveis em todas as implementações do .NET que estiver usando.

E é aí que entra o .NET Standard. O .NET Standard é uma especificação. Cada versão do .NET Standard define o conjunto de APIs que todas as implementações do .NET devem fornecer para estarem em conformidade com essa versão. Você pode considerar isso ainda como uma outra pilha do .NET, exceto que não pode compilar aplicativos para ela, apenas bibliotecas. É a implementação do .NET que você deve usar para as bibliotecas que deseja referenciar de todos os locais.

Você provavelmente está se perguntando quais APIs são cobertas pelo .NET Standard. Se estiver familiarizado com o .NET Framework, é provável que você esteja familiarizado com o BCL, que mencionei anteriormente. O BCL é o conjunto de APIs fundamentais que são independentes das estruturas de interface de usuário e dos modelos de aplicativos. Ele inclui os tipos primitivos, E/S de arquivos, rede, reflexão, serialização, XML e muito mais.

Todas as pilhas do .NET implementam alguma versão do .NET Standard. Como regra geral, quando uma nova versão de uma implementação do .NET for produzida, ela geralmente implementará a versão mais recente disponível do .NET Standard.

Uma boa analogia é o HTML e os navegadores: pense na especificação HTML como o .NET Standard e os diferentes navegadores como as implementações do .NET, como o .NET Framework, o .NET Core e o Xamarin.

Neste ponto, você provavelmente deve estar curioso de como usar o .NET Standard. Na verdade, você já o tem. Lembra-se quando criamos a biblioteca de classes de lógica anteriormente? Vamos examinar o arquivo de projeto:

$ cd logic
$ cat logic.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

</Project>

Vamos contrastar isso com o arquivo de projeto do aplicativo de console "hello":

$ cd ..\hello
$ cat hello.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <ProjectReference Include="..\logic\logic.csproj" />
  </ItemGroup>

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>

</Project>

Como você pode ver, a biblioteca lógica tem um valor netstandard2.0 para TargetFramework, enquanto o aplicativo de console tem um valor netcoreapp2.0. A propriedade TargetFramework indica qual implementação do .NET você está direcionando. Portanto, o aplicativo de console está direcionando o .NET Core 2.0, enquanto a biblioteca está direcionando o .NET Standard 2.0. Isso significa que você pode fazer referência à biblioteca lógica não só de um aplicativo .NET Core, mas também de um aplicativo compilado para .NET Framework ou Xamarin.

Infelizmente, a maioria das bibliotecas disponíveis atualmente ainda não estão direcionadas para o .NET Standard. A maioria delas está direcionada para o .NET Framework. É claro que nem todas as bibliotecas podem (ou mesmo devem) estar direcionadas para o .NET Standard. Por exemplo, uma biblioteca que contém controles do Windows Presentation Foundation (WPF) precisa ter como destino o .NET Framework porque a IU não faz parte do padrão. No entanto, muitas das bibliotecas de uso geral apenas têm como objetivo o .NET Framework por terem sido criadas quando o .NET Standard simplesmente ainda não existia.

Com o .NET Standard 2.0, o conjunto de APIs é grande o suficiente para que a maioria das bibliotecas de uso geral (se não todas) possam direcionar o .NET Standard. Como resultado, 70 por cento de todas as bibliotecas existentes hoje no NuGet só usam APIs que agora fazem parte do .NET Standard. Ainda assim, apenas uma fração delas é explicitamente marcada como compatível com o .NET Standard.

Para que os desenvolvedores fiquem desbloqueados de usá-los, um modo de compatibilidade foi adicionado. Se você instalar um pacote NuGet que não oferece uma biblioteca para sua estrutura de destino, nem fornece uma para o .NET Standard, o NuGet tentará retornar ao .NET Framework. Em outras palavras, você pode fazer referência às bibliotecas do .NET Framework como se elas estivessem direcionando o .NET Standard.

Mostrarei o que significa isso. No meu exemplo, vou usar uma biblioteca de coleção popular chamada PowerCollections, que foi escrita em 2007. Ela não é atualizada há um tempo e ainda tem como destino o .NET Framework 2.0. Vou instalar isso a partir do NuGet no aplicativo hello:

$ dotnet add package Huitian.PowerCollections

Essa biblioteca fornece tipos de coleções adicionais que o BCL não oferece, como uma sacola, que não faz garantias de pedidos. Vamos alterar o aplicativo Hello para usar isso, como mostrado na Figura 6.

Figura 6 Exemplo de aplicativo usando o PowerCollections

using System;
using Wintellect.PowerCollections;
namespace hello
{
class Program
{
static void Main(string[] args)
{
var data = new Bag<int>() { 1, 2, 3 };
foreach (var element in data)
Console.WriteLine(element);
}
}
}

Se executar o programa, você verá o seguinte:

$ dotnet run
hello.csproj : warning NU1701: Package 'Huitian.PowerCollections 1.0.0' was restored using '.NETFramework,Version=v4.6.1' instead of the project target framework '.NETCoreApp,Version=v2.0'. This may cause compatibility problems.
1
3
2

Então, o que aconteceu? O aplicativo Hello está direcionando o .NET Core 2.0. Como o .NET Core 2.0 implementa o .NET Standard 2.0, ele também possui o modo de compatibilidade para referenciar as bibliotecas do .NET Framework. No entanto, nem todas as bibliotecas do .NET Framework funcionarão em todas as implementações do .NET. Por exemplo, elas podem usar o Windows Forms ou as APIs do WPF. O NuGet não tem como saber disso, então, ele lhe emitirá uma mensagem de aviso para que você esteja ciente dessa situação e não perca seu tempo solucionando problemas que possam resultar disso.

Observe que você receberá esse aviso sempre que estiver compilando. Isso evitará o problema durante a instalação do pacote onde você simplesmente não percebe o aviso, ou esquece dele.

Claro, não há nada pior do que avisos não acionáveis que você precisa ignorar toda vez que compilar. Então, a ideia aqui é que depois de validar seu aplicativo, você possa desabilitar o aviso para esse pacote. Como o aplicativo está funcionando bem (ele imprimiu corretamente o conteúdo da sacola que você criou), você agora pode suprimir o aviso. Para isso, edite o arquivo hello.csproj e adicione o atributo NoWarn à referência do pacote:

<PackageReference Include="Huitian.PowerCollections" Version="1.0.0" 
  NoWarn="NU1701" />

Se você agora executar o aplicativo novamente, o aviso deverá ter desaparecido. Se instalar outro pacote que use o modo de compatibilidade, você receberá o aviso para esse pacote que também poderá ser suprimido.

A nova ferramenta também permite que projetos de biblioteca de classes produzam pacotes NuGet como parte da compilação. Isso torna muito mais fácil compartilhar suas bibliotecas com o mundo (enviando por push para o nuget.org) ou apenas dentro da sua organização (enviando por push para o seu próprio feed de pacote no Visual Studio Team Services ou MyGet). Os novos projetos também suportam o pacote multiplataforma, o que permite que você compile um único projeto para várias implementações do .NET. Isso significa que você pode usar a compilação condicional (#if) para adaptar a biblioteca a implementações específicas do .NET. Ela também permite que você crie wrappers do .NET Standard para APIs específicas de plataforma. No entanto, tudo isso está além do escopo deste artigo.

Conclusão

O .NET Standard é uma especificação de APIs que todas as implementações do .NET devem fornecer. Ele traz consistência para a família .NET e permite que você compile bibliotecas que poderá usar a partir de qualquer implementação do .NET. Ele substitui PCLs para a compilação de componentes compartilhados.

O .NET Core é uma implementação do .NET Standard que é otimizado para compilar aplicativos de console, aplicativos Web e serviços de nuvem usando o ASP.NET Core. Seu SDK é fornecido com ferramentas eficientes que, além do desenvolvimento do Visual Studio, suportam um fluxo de trabalho completo de desenvolvimento baseado em linha de comando. Você poderá saber mais sobre eles em aka.ms/netstandardfaq e aka.ms/netcore.


Immo Landwerth é gerente de programa da Microsoft e trabalha no .NET. Seu foco é o .NET Standard, o design de BCL e de API.


Discuta esse artigo no fórum do MSDN Magazine