5 Comments

It started as a quick evening project (HTML Pager for Bootstrap), one class library I needed for some web projects to generate Bootstrap-happy pager HTML, and it ended up in hours of chasing weird VS2015 project configuration issues. I just wanted to package Core project into nuget and use it from MVC6 Core projects and also "old" full .NET 4.6 web projects (MVC5). Soon after I started splitting projects and importing DLLs, Visual Studio complained and I just couldn't reference Core project from my MVC 5 web app!

Scott Hanselman explained similar in his blog post, but I needed the exact opposite thing!

Maybe .NET Core is in RTM, but it reminded me that tooling support is still in deep Preview 2 state where half of things just doesn't work, meaning there's no UI support for them.

Plan is to have 3 packages:

  1. Core class library, with no special dependencies to MVC, but just pager renderer class (xproj)

  2. MVC 5 html helper wrapper (full .net 4.6, csproj), dependency to MVC 5

  3. MVC 6 html helper wrapper (core, netstandard1.6, xproj), dependency to MVC 6

For 3), its easy since xproj can be referenced from another xproj, but Core project can't be referenced from 4.6 (csproj) project! The trick is to package core bits into nuget and then import that into csproj.

projectstructure

Core class library needs to specify both frameworks in project.json, like this:

"frameworks": {
   "netstandard1.3": {
     "dependencies": {
       "NETStandard.Library": "1.3.0"
     }
   },
   "net46": {
     "dependencies": { "System.Runtime": "4.0.20" }
   }
}

It has two frameworks, so library can be used from .net 4.6 and Core. I'm using Func<,> and System.Runtime package has to be added to net46 project.

The problem is, you can Add Project Reference to it from other Core (xproj) project, but not from standard 4.6 project. To use it from 4.6, a package needs to be created and added through nuget. Every time I change something in the Core lib, I have to create the package and re-install it to 4.6 project.

In my case, I added small .bat file to the Core project root, which creates package into a common folder:

dotnet pack -o ../../InternalRepository

Dotnet pack command uses information from project.json to create the package, like id, title, dependency libs, etc. Convenient UI tool for managing nupkg and nuspec files is Package Manager Explorer https://github.com/NuGetPackageExplorer/NuGetPackageExplorer

InternaRepository is folder referenced by solution nuget.config, used by all projects in the solution:

 <configuration>
  <packageSources>
    <add key="Local" value="InternalRepository" />
    <add key="NugetV3" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
  </packageSources>
</configuration>

Note: if you change something in Core project, package needs to be created again and re-installed into 4.6 project. One way is updating build semver (1.0.x) and then update the package, other way is re-installing existing package with (in Package-Manager Console):
Update-Package JA.Pagination -reinstall

Why I didn't just pack Core package into nuget and used that? I wanted to have extension methods for Razor, so pager can be called with just Html.RenderPager(...), instead of Html.Raw(Pager.Build(...).Render()).

Since MVC5 and MVC6 have different HtmlHelper classes, I needed two additional projects, each one targeting different version of MVC and adding extension method to specific HtmlHelper class.

To be honest, I’m still not sure if this is the best way of managing 4.6 and Core projects in the same solution, but it’s the only thing that worked! In case somebody knows better way, I would be happy to hear about it, so please post the comment!

Full source code is available on Github.

Comments

Comment by nemke

Hudo, how were you able to reference "NETStandard.Library": "1.3.0" version 1.3, when there is only 1.6 available?

nemke
Comment by Travis

This is exactly what I needed. I'm stunned that this scenario is not front and center in .net core docs... Both directions mentioned (Scott and yours) are key and it's just not as simple as it should be. Wasted hours down this path (like many others I suspect). Thanks for the article. We appreciate the effort!

Travis
Comment by Hudo

@nemke i just took lowest netstandard that supports .net 4.6, which is 1.3. But 1.6 is used, since that is supported by Core 1.0 - and 1.6 implements all 1.3 API surface. At least, that's my understanding!

@Travis Thanks for the comment. Yes, it took me few hours until i found this "way", even I'm sure MS is going to simplify all this when tooling supports goes rtm. Too bad there are no official docs on this:/

Comment by nemke

My understanding is that NETStandard.Library is different than netstandard API. NETStandard.Library is metapackage on nuget that goes from 1.5 to 1.6 version.

"Metapackages are a NuGet package convention for describing a set of packages that are meaningful together. They represent this set of packages by making them dependencies. They can optionally establish a framework for this set of packages by specifying a framework."

For example netstandard1.3 just specifies target framework API to be used. So there is no NETStandard.Library version 1.3. It's only one metapackage, version 1.6, but you can target any netstandard1.x API with that metapackage.

I was confused with all this also. Here is a great link docs.microsoft.com/.../packages

nemke
Comment by nemke

"It may seem strange to target netstandard1.3 but use the 1.5.0 version of NETStandard.Library. It is a valid use-case, since the metapackage maintains support for older netstandard versions. It could be the case you've standardized on the 1.5.0 version of the metapackage and use it for all your libraries, which target a variety of netstandard versions. With this approach, you only need to restore NETStandard.Library 1.5.0 and not earlier versions.

The reverse would not be valid: targeting netstandard1.5 with the 1.3.0 version of NETStandard.Library. You cannot target a higher framework with a lower metapackage, since the lower version metapackage will not expose any assets for that higher framework. The [versioning scheme] for metapackages asserts that metapackages match the highest version of the framework they describe. By virtue of the versioning scheme, the first version of NETStandard.Library is v1.5.0 given that it contains netstandard1.5 assets. v1.3.0 is used in the example above, for symmetry with the example above, but does not actually exist."

nemke