Como você multi-target uma biblioteca de classes .NET Core com csproj?

94

Quando o .NET Core ainda usava o project.jsonformato, você poderia construir uma biblioteca de classes visando vários frameworks (por exemplo, net451, netcoreapp1.0).

Agora que o formato oficial do projeto está csprojusando MSBuild, como você especifica várias estruturas para o destino? Eu estou tentando olhar para este a partir das configurações do projeto no VS2017, mas eu sou capaz de atingir apenas um único quadro dos quadros .NET núcleo (que nem sequer listar as outras versões do .NET Framework completo que eu não tenha instalado) :

insira a descrição da imagem aqui

Gigi
fonte
Isso não é o que deveria parecer, você deve obter as opções do .NETStandard 1.x listadas no menu suspenso. Não está muito claro como isso aconteceu, certifique-se de escolher o modelo de projeto correto para começar. Deve ser "Class Library (.NET Standard)". Parece que você escolheu o modelo de aplicativo de console e começou a alterar as propriedades, não da maneira correta. Se você de fato usou o modelo da Biblioteca de Classes, a instalação não foi bem.
Hans Passant
Na verdade, selecionei Class Library (.NET Core).
Gigi
2
Certo, então esse é o errado se você quiser multi-alvo. Você deve escolher um .NETStandard para tornar a biblioteca utilizável em mais de uma plataforma.
Hans Passant
Isso esclarece tudo. Você pode escrever uma resposta a partir de seus comentários, se desejar.
Gigi

Respostas:

119

Você precisa editar manualmente o arquivo de projeto e adicionar s ao TargetFrameworks padrão e basicamente alterá-lo para TargetFrameworks . Então você menciona o Moniker com um ; separador.

Além disso, você pode colocar as referências do pacote Nuget em um ItemGroup condicional manualmente ou usando o VS Nuget Package Manager.

Esta é a aparência do seu .csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>netstandard1.6;net452</TargetFrameworks>
  </PropertyGroup>

  <ItemGroup Condition="'$(TargetFramework)' == 'net452'">
    <PackageReference Include="Microsoft.Azure.DocumentDB">
      <Version>1.12.0</Version>
    </PackageReference>
  </ItemGroup>

  <ItemGroup Condition="'$(TargetFramework)' == 'netstandard1.6'">
    <PackageReference Include="Microsoft.Azure.DocumentDB.Core">
    <Version>1.1.0</Version>
    </PackageReference>
  </ItemGroup>
</Project>

Outra solução alternativa que faço atualmente por causa da documentação ausente é que eu crio um projeto no VS2015 e formo o project.json usando a documentação disponível e o intellisense, então abro a solução no VS2017 e uso a atualização embutida. Em seguida, examinarei o arquivo csproj para descobrir como fazer essa configuração acontecer.

Múltiplos alvos mais esotéricos sem um Moniker :

Microsoft:

PCLs não são recomendados +

Embora PCLs sejam suportados, os autores de pacotes devem oferecer suporte ao netstandard. O .NET Platform Standard é uma evolução dos PCLs e representa a portabilidade binária entre plataformas usando um único moniker que não está vinculado a um moniker estático, como portable-a + b + c monikers.

Se você quiser atingir um perfil portátil não tem um pré-definidos apelido tão Profiles portáteis também não pode inferir TargetFrameworkIdentifier, TargetFrameworkVersione TargetFrameworkProfile. Além disso, uma constante do compilador não é definida automaticamente. Finalmente, você deve adicionar todas as referências de assembly, nenhuma delas é fornecida por padrão.

O exemplo abaixo foi tirado de um projeto que usava a dynamicpalavra-chave, portanto, além disso, precisava da Microsoft.CSharpmontagem, portanto, você pode ver como é referência para diferentes destinos.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>netstandard1.5;net40;portable40-net45+sl5+win8+wp8</TargetFrameworks>
  </PropertyGroup>

  <PropertyGroup Condition="'$(TargetFramework)'=='portable40-net45+sl5+win8+wp8'">
    <TargetFrameworkIdentifier>.NETPortable</TargetFrameworkIdentifier>
    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
    <TargetFrameworkProfile>Profile158</TargetFrameworkProfile>
    <DefineConstants>$(DefineConstants);PORTABLE158</DefineConstants>
  </PropertyGroup>

  <ItemGroup Condition="'$(TargetFramework)'=='netstandard1.5'">
    <PackageReference Include="Microsoft.CSharp" Version="4.3.0" />
    <PackageReference Include="System.ComponentModel" Version="4.3.0" />
  </ItemGroup>

  <ItemGroup Condition="'$(TargetFramework)'=='net40'">
    <Reference Include="Microsoft.CSharp" />
  </ItemGroup>

  <ItemGroup Condition="'$(TargetFramework)'=='portable40-net45+sl5+win8+wp8'">
    <Reference Include="Microsoft.CSharp" />
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Windows" />
  </ItemGroup>
</Project>
Uma vaia
fonte
1
Você tem que fazer isso editando o csproj manualmente ou pode ser feito via VS?
Gigi
1
A segmentação múltipla do @Gigi precisa ser feita manualmente. Os pacotes Nuget podem ser feitos via VS2017 ou manualmente.
Aboo
2
Eu estava tendo o mesmo problema e tudo funcionou bem depois que adicionei o s a <TargetFramework>. Realmente gostaria que essas coisas fossem melhor documentadas.
Negorath
2
@AsadSaeeduddin é provável que o Resharper esteja mostrando os rabiscos e não o Visual Studio. O $ (TargetFramework) é usado apenas para tornar um ItemGroup disponível para um framework / Moniker específico.
Aboo
2
@ORMapper se o pacote NuGet for construído corretamente, isso deve acontecer automaticamente quando você importar. O que significa que se NuGetPackageA já oferece suporte a vários frameworks, você não precisa colocá-lo em um grupo de itens condicionados. Agora, se você precisar fazer referência ao PackageA para .net framework e PackageB para .net core, isso precisa ser colocado no grupo de itens condicionados. Não há opção na interface a partir de hoje (outubro de 2017).
Aboo,
24

Você pode editar manualmente o .csprojarquivo para isso e definir TargetFrameworks(não TargetFramework) a propriedade.

<TargetFrameworks>net451;netstandard1.4</TargetFrameworks>

Por exemplo, consulte EFCore.csproj: https://github.com/aspnet/EntityFrameworkCore/blob/951e4826a38ad5499b9b3ec6645e47c825fa842a/src/EFCore/EFCore.csproj

Caminhante noturno
fonte
9
Obrigado! Isso me mata. Em "The Elements of Programming Style" de Brian Kernigan, escrito há quatro décadas, ele fala sobre os erros de usar variáveis ​​que diferem por uma única letra no final. Teria ficado muito mais claro se o nome fosse "TargetFrameworkList".
howardlo
O exemplo que você aponta não inclui uma propriedade <TargetFrameworks>.
RenniePet
@RenniePet, obrigado! O arquivo do projeto mudou com o tempo. Mudei o link para um commit concreto, onde existe <TargetFrameworks>.
Night walker
12

Na verdade, selecionei Class Library (.NET Core).

Esse não é o modelo de projeto que você deseja se sua biblioteca precisar trabalhar em vários destinos de plataforma. Com este modelo de projeto, sua biblioteca só pode ser usada em um projeto voltado para .NETCore. A abordagem da biblioteca PCL foi aposentada, agora você precisa escolher um .NETStandard.

Você faz isso iniciando o projeto com o modelo de projeto "Class Library (.NET Standard)". Agora você tem a opção de escolher a versão .NETStandard. A grade de compatibilidade atual está aqui .

Esperançosamente, eles manterão o artigo vinculado atualizado. Isso está em evolução, o .NETStandard 2.0 foi elaborado, mas ainda não foi lançado. Visado para o segundo trimestre de 2017, provavelmente no final da primavera, atualmente mostra 97% concluído. Eu ouvi os designers dizendo que o uso de 1.5 ou 1.6 não é recomendado, não é compatível o suficiente com 2.0

Hans Passant
fonte
Como isso funciona se você tiver dependências diferentes para estruturas de destino diferentes? Quero dizer, project.jsonvocê pode especificar dependências específicas para uma estrutura de destino.
Gigi
1
Tenho 90% de certeza de que você precisa esquecer que isso existiu. Era uma bagunça confusa que era apenas uma medida temporária para inicializar o .NETCore. Use a grade de compatibilidade que vinculei.
Hans Passant
Eu suspeitei disso. Muito obrigado por esclarecer!
Gigi
4
@HansPassant multi-target ainda é a melhor opção se você tiver código legado e, ao mesmo tempo, seu desenvolvimento greenfield pode ser feito em um dos mais recentes tipos de framework completos ou dotnetcore.
Stefano Ricciardi
1
Nem mesmo o greenfield é necessariamente compatível com o .NET Core ainda. Estou usando os Aplicativos de Função do Azure, mas sua versão do .NET Core só oferece suporte a alguns gatilhos. (Eu preciso de gatilhos do Service Bus, então estou preso ao .NET Framework, e uma biblioteca de funcionalidade de negócios muito grande pode acabar como multi-destino.) A plataforma da Microsoft está uma bagunça emaranhada agora. É o equivalente moderno do DLL Hell.
McGuireV10
5

Eu fiz um guia para iniciantes para estrutura de rede de múltiplos alvos e netcore que começa com a correção simples de 1 linha e então o conduz por cada uma das complicações.

A abordagem mais simples é fazer com que um netcore ou netstandard target funcione primeiro. Em seguida, edite o arquivo csproj e execute essas etapas para os outros destinos.

  1. Aprenda sobre as seções condicionais em seu arquivo csproj, para que você possa declarar dependências diferentes para cada destino. Crie seções condicionais para cada destino.
  2. Adicione <Reference />spara System. * Dlls para quaisquer alvos de netframework apenas lendo o que as mensagens de erro de compilação dizem que está faltando.
  3. Lide com as dependências do NuGet <PackageReference />snos casos em que não sejam iguais para cada destino. O truque mais fácil é reverter temporariamente para a segmentação única para que a GUI manipule as referências do Nuget corretamente para você.
  4. Lide com código que não compila em todos os alvos, aprendendo uma variedade criativa de técnicas, soluções alternativas e poupadores de tempo.
  5. Saiba quando cortar suas perdas quando o custo de adicionar mais alvos for muito alto.
Chris F Carroll
fonte