Creating multi-platform library with .Net Standard

I want to note this was my first stab at getting a workflow for cross-platform libraries using the new technologies. I don't consider any of these methods the correct way to do things, but they represent my experience with it so far.

I've had a fairly unstable relationship with .Net recently. I support the push to make things cross-platform. I understand that change is tricky. However, I feel like there is a lack of the usual Microsoft prep-work when it comes to the new offerings. As a person in charge of technology decisions it's important I understand how all the new updates work so I can decide what we upgrade and when.

With that being said I set out to create a library that would work with my existing .Net projects, and also be able to work with Core projects we plan to build in the future. What a ride.

Getting it Working

I'll try to get to the solution quickly without traversing the rage I felt trying to put this together. I have a fresh copy of Visual Studio 2017, and I'm ready to go.

.Net Standard is supposed to unify the different versions of the .Net Framework together. Figuring that out took some research on its own, but in theory, you can run a .Net Standard library on Core and traditional framework items. I started there. Create a new Visual Studio solution, add a new Class Library (.Net Standard). Once that is done create a class that does something so you can test.

public class StringGenerator
{
    public string BuildString()
    {
        return "Hello World";
    }
}

We are going to want to have some tests. After all, we try to have some quality, and since we are fighting the framework we don't want to ALSO fight the code. Create an xUnit Test Project (.Net Core). .Net Core is very important. You won't have Visual Studio Test Explorer support if you house the unit tests in a .Net Standard library because... reasons. I'm sure there is a good one, and you can probably get it working, but this was the easy way for me. Add a unit test.

public class StringGeneratorTests
{
    [Fact]
    public void BuildString_Success()
    {
        StringGenerator generator = new StringGenerator();

        var result = generator.BuildString();

        Assert.Equal("Hello World", result);
    }
}

I'm publishing this library as a NuGet package when a release tag is added to GitHub. I threw a quick build script together to manage that. If you aren't using CI or anything you can see some of the commands I'm using:

version: 1.0.{build}
clone_depth: 3
image: Visual Studio 2017
skip_branch_with_pr: true
configuration: Release

branches:
  except:
    - gh-pages

before_build:
  - cmd: cd src
  - cmd: dotnet restore
  - cmd: cd ..

build:
  project: src\App.HelloCore.sln

after_build:
  - cmd: dotnet pack src\App.HelloCore\App.HelloCore.csproj --output artifacts\bin

artifacts:
  - path: '**\*.nupkg'

nuget:
  account_feed: true

test_script:
  - dotnet test .\src\App.HelloCore.Tests\App.HelloCore.Tests.csproj

deploy:
  - provider: NuGet
    server: https://www.myget.org/F/private/api/v2/package
    api_key:
      secure: SUPERSECUREKEY
    skip_symbols: true
    artifact: /.*\.nupkg/
    on:
      branch: master
      appveyor_repo_tag: true

At this point, you can publish the Nuget package and use it... ish. You need to follow .Net Standards versioning to see what version of standard works with core, etc. Andrew Lock has a table in his article that shows the current version support.

Now you can create a console app to consume the package (Make sure the console app is a version that supports the .Net Standard version being used):

class Program
{
    static void Main(string[] args)
    {
        var generator = new StringGenerator();

        Console.WriteLine(generator.BuildString());

        Console.ReadLine();
    }
}

Multi-platform building

The first issue that I see is when creating a .Net Framework console app the NuGet restore brings in a TON of packages. I assume this is to map standard libraries to the .Net ones. Not sure yet. I'll update when I know for sure. Either way, I don't like that so I set off to make the project Multi-platform.

Without getting into another rant about project.json vs .csproj here is how I modified the project file for the library in order to build under both .NetStandard and the native .Net framework.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFrameworks>netstandard1.4;net45</TargetFrameworks>
    <Version>1.0.2</Version>
  </PropertyGroup>

</Project>

That seemed to work? I know I will have dependency issues when I move on to libraries with deeper dependency chains, but bringing in that NuGet package worked for me.

Things that tripped me up

  1. Inconsistency - .json vs .csproj. Use core as your library. Use standard as your library. Build with dnx I mean dotnet I mean MSBuild. There are good reasons for all of this, but it is very tough to parse as a first time user. There is a certain level of "I'm only doing this because I love c#" which could potentially wear on the team over time.
  2. Microsoft documentation - All of the articles explain how to do different things using the .json configuration format instead of the .csproj. Some things work, and some things don't work. All the documentation is very obscure. I ended up piecing solutions from various Github issues.
  3. Tooling support - The project files support multi-platform building, but Visual Studio doesn't see it in the properties section. Building it wrong produces really obscure errors.

I think a lot of this will resolve with time. A lot of people are working really hard to make cross-platform c#, and for that I thank you. We are just having a little more upgrade issues than I am used to.

I hope this helps someone get a little closer. I'll try to post more articles and update this one when I find the most consistent ways to do this simply.