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.


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:

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

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.


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?

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!

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:/

Post comment