Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs
Contact   •   Articles   •   Products   •   Support   •   Search
Ad-free experience sponsored by:
ASPOSE - the market leader of .NET and Java APIs for file formats – natively work with DOCX, XLSX, PPT, PDF, images and more

Creating multi-target NuGet Packages with vNext


One feature that is going to make it much easier to create multi-targeted NuGet packages is the ability of the vNext platform to package compiled code directly into NuGet packages. By default vNext applications don’t compile to disk but rather create source code in the AppCode folder. A running application  then reads this source code and compiles the code on the fly via Roslyn to execute it from memory.

However, if you build class libraries you can also optionally write out the result to disk, which creates a NuGet package. That’s cool all by itself, but what’s even nicer is the fact that you can create multiple build targets for different versions inside of that NuGet package. You can create output for vNext Full and Core and even standard .NET and PCL components – all from a single project!

It’s essentially very easy and natural to produce a NuGet package like this:

NuGetPackage

This package contains output for vNext Full CLR (aspnet50), vNext Core CLR (aspnetcore50) and the full .NET Runtime (net45).

If you’ve built multi-targeted assemblies and packages before you probably know how much of a pain this was in previous versions of .NET and Visual Studio. You either had to hack the MSBUILD process or else use separate projects, Solution build targets or separate solutions altogether to accomplish this. In vNext you can do this with a few simple project settings. You can simply build your project with output options turned on both from within Visual Studio or from the command line without using MsBuild (yay!) and produce a NuGet package as shown above.

That’s pretty awesome!

Creating a Project

As part of my exploration of vNext I’m in the process of moving a few of my helper libraries to vNext. This is turning out to be a challenge if you plan on supporting the Core CLR which has a fairly restricted feature set. The vast percentage of code works as is, but there’s also a fair bit – and some of it surprising - that doesn’t run as is. And there’s an awful lot of looking for packages and namespaces to get the features that I know that are there…

For initial testing I used my Westwind.Utilities library and just pulled out one of the classes – the StringUtils class. I used this one because it has very few system dependencies so I hoped it would just run as is even under vNext. Turns out it doesn’t – even this very basic class has a few pieces that don’t exist under vNext or at least not with the same signatures. Which makes it perfect for this example as I have a few methods I need to bracket out for Core CLR usage.

Setting up a Library Project

In order to set this up the first thing I did is create a new Class library project in Visual Studio.

NewClassLibraryProject

By default Visual Studio creates a project.json file with the two ASP.NET nNext targets (aspnet50 and aspnetcore50). In addition I explicitly added the .NET 4.5 target (net45) in project.json (which is actually what’s shown in the project above).

Here’s what project.json looks like:

{ "version": "1.0.0-*", "dependencies": { }, "frameworks": { "net45": { "dependencies": { } }, "aspnet50": { "dependencies": { } }, "aspnetcore50": { "dependencies": { "System.Runtime": "4.0.20-beta-*", "System.IO": "4.0.10-beta-*", "System.Runtime.Extensions": "4.0.10-beta-*", "System.Text.Encoding": "4.0.0-beta-*", "System.Text.RegularExpressions": "4.0.0-beta-*", "System.Linq": "4.0.0-beta-*", "System.Reflection": "4.0.0-beta-*", "System.Reflection.Extensions": "4.0.0-beta-*", "System.Reflection.TypeExtensions": "4.0.0-beta-*", "System.Threading.Thread": "4.0.0-beta-*", "System.Threading.Tasks": "4.0.0-beta-*", "System.Globalization": "4.0.0-beta-*", "System.Resources.ResourceManager": "4.0.0-beta-*" } } } }

The three highlighted targets correspond to the References nodes in Visual Studio project and correspond to the 3 different build targets of the project.

Note that I also explicitly have to reference any of the BCL components I’m using in my component for the Core CLR  target.  The other two are getting these same components from GAC components of the full CLR so they don’t need these. Since I’m including a ‘classic’ .NET 4.5 target here I have to be careful of how I add references – all vNext references that apply to both vNext Core and Full CLR need to be explicitly assigned to their dependency nodes, while any dependencies of the the full .NET runtime needs to go in its dependency section.

If you target only the two vNext versions you can use the global dependency node for any shared components which is a lot less verbose.

Note that for the Core CLR I have to manually add all the little tiny packages for the BCL classes that used to live in mscorlib and system. I added these as I started tweaking my component – they aren’t there by default. The only default component is System.Runtime. While adding every little thing is a pain it does help with modularization where you get just what you ask for and nothing more. But to be honest I find it hard to believe that anything less than what I have above would ever not be used by either my own code or any referenced components (minus the regex maybe) so maybe this is just getting a little too granular.

If you’re building projects that use more high level components (like EntityFramework or the new ASP.NET MVC) you’ll find that most of the things you need to reference are already referenced by those higher level components, so some of this minute package referencing goes away. But if you’re writing a core component that has minimal non-system dependencies you’ll find your self doing the NuGet Package Hula!

To help with finding Packages and Namespaces you might find  http://packagesearch.azurewebsites.net  useful. Maintained by a Microsoft employee ( Glenn @condrong)  this tool lets you search for packages and namespaces in the vNext BCL/FCL libraries by name:

packageSearch 

Conditional Code

Once you have your targets defined you can start adding some code. If your code just works across all the targets defined you’re done. Writing greenfield code it’s not too difficult to write code that works across all platforms.

In my case however, I was backporting an existing component and I ran into a few code references that didn’t work in the Core CLR.

If you have multiple targets defined in your application, vNext will compile your code to all 3 targets and shows you errors for any of the targets that fail. In my case I ran into problems with various System.IO classes like StreamWriter and MemoryStream that don’t exist (yet?) in vNext. In Visual Studio the compilation error window shows the errors along with the target that failed:

CompileErrors

Note the first 3 errors refer to StreamReader related errors. Apparently StreamReader doesn’t exist in vNext or I’m missing a package reference. I can see that the problem is in aspnetcore50 based on the project name in the Project column.

I can now also look at that code in the Visual Studio editor and see the StreamWriter reference error there for Core CLR along with an overview of the code I’m calling and which targets are supported and which ones won’t work (nice):

NoStreamWriter

It’s a bit odd that StreamWriter is not working. In fact most of the stream related classes in System.IO don’t appear to be there. It makes  me think that either I’m missing a package or this is still under heavy construction by Microsoft. Either way it demonstrates the point that there may be things that may not work with Core CLR.

To get around this I can now choose to bracket that code like this effectively removing this function (or alternately rewrite the function using some other code). For now I’m just going to bracket out the offending method altogether like this (with a //TODO to come back to it later):

#if !ASPNETCORE50
        /// <summary>
        /// Simple Logging method that allows quickly writing a string to a file
        /// </summary>
        /// <param name="output"></param>
        /// <param name="filename"></param>
        public static void LogString(string output, string filename)
        {            
            StreamWriter Writer = File.AppendText(filename);
            Writer.WriteLine(DateTime.Now.ToString() + " - " + output);
            Writer.Close();
        }
#endif

If I then recompile or pack the project, I’ll get no errors.

The compiler constants available for the three target versions in this project are: ASPNET50,ASPNETCORE50,NET45. Each of these #define constants are implicitly created as upper case versions of the defined frameworks in project.json. You can use either of these to take particular action or bracket code for compilation.

Using the Component in another Project (Source Code)

If I flip over to my Web project and want to now use my component I can simply add a NuGet reference to it like this:

"dependencies": {
    "Microsoft.AspNet.Hosting": "1.0.0-beta2-*",
    "Microsoft.AspNet.Server.WebListener": "1.0.0-beta2-*",
    "Microsoft.AspNet.Server.IIS": "1.0.0-beta2-*",
    "EntityFramework": "7.0.0-beta2-*",
    "EntityFramework.SqlServer": "7.0.0-beta2-*",
    "Microsoft.Framework.ConfigurationModel.Json": "1.0.0-beta2-*",
    "Microsoft.AspNet.Mvc": "6.0.0-beta2-*",
    "AlbumViewerBusiness": "" 
"Westwind.Utilities": "", },

Once added I can now use in code just like any other package. When this Web project uses this ‘project reference’ it pulls the source code for the Westwind.Utilities and compiles it on the fly and executes it.

I can also get the same Runtime version information from IntelliSense that tells me whether a feature is supported for one of the versions I’m targeting in the Web project. My Web project targets vNext Full and Core CLR so if I try to use the StringUtils.LogString() method I get this:

LibraryNoSupportedMethod

You can see here that LogString is available for Full CLR operation, but not for Core CLR operation and IntelliSense lets you know. The compiler too will let you know that if you use LogString and targeting the Core CLR you will get an error.

As you can imagine bracketing code out is not always a good idea – it makes it much harder to reuse existing code or migrate code. But it’s quite common as you can see by the heavy refactoring that’s happening in the core BCL/FCL libraries that Microsoft is reworking and the many missing features that just aren’t there (yet?).

Building a NuGet Package

When I built my project above I simply use the default build operation which doesn’t actually generate any output. By default vNext runs the code directly from source code and compiles it into memory. In vNext the compiler acts more as a syntax checker than an actual compiler when you click the Build button in Visual Studio.

You can however force the compiler to generate output to disk by setting an option which creates – you guessed it – a NuGet package rather than just an assembly. If I go back to the Westwind.Utilities project now and click on the Project Properties I can get this option (which is very likely to get a lot more options for package creation):

buildOutput

Now if I build the project I get my NuGet package built:

NuGetPackage

I can now take that package and either publish it or share it as needed. Before publishing I could also go in and customize the nupkg using the NuGet Package Explorer:

 PackageExplorer

Note that the current Package Explorer doesn’t understand the new vNext runtime versions yet, but that’ll change and hopefully Microsoft will consider moving some of this functionality right into Visual Studio and the build dialog to edit and adjust the package meta data.

Packages made easy

Creating multi-targeted libraries is never easy, but these new features in vNext at least make it a lot easier to manage the process of building them from a single source code base without having to heavily tweak the build process – it just works out of the box.


I'll be at DevIntersection in Vegas this fall giving sessions on ASP.NET Core with Angular and Localization. Thinking of coming? Use discount code STRAHL and save a few bucks. If you do be sure to stop by and say hello!

ASP.NET DevIntersection 2017. Rick Strahl Coupon Code

Posted in ASP.NET vNext  

The Voices of Reason


 

Khalid Abuhakmeh
December 02, 2014

# re: Creating multi-target NuGet Packages with vNext

"It’s a bit odd that StreamWriter is not working. In fact most of the stream related classes in System.IO don’t appear to be there. It makes me think that either I’m missing a package or this is still under heavy construction by Microsoft."

Yeah... tell me about it. I ran into this and basically couldn't go much further with core. Also there is not way to convert an array of bytes back into an object with Core from what I've seen (so far). There is a lot still churning and probably worth waiting out, but the impulse to use the new and shiny is so powerful.

Daniel Plaisted
December 02, 2014

# re: Creating multi-target NuGet Packages with vNext

When porting code to .NET Core, I'd suggest using our .NET Portability Analyzer to determine which APIs aren't supported in .NET Core, and in common cases what you can replace them with: http://blogs.msdn.com/b/dotnet/archive/2014/08/06/leveraging-existing-code-across-net-platforms.aspx

I'm not sure why StreamReader and StreamWriter weren't working for you. Are you sure the reference to the System.IO package was working correctly?

For the File class (and hence the File.AppendText method), you need to reference the System.IO.FileSystem package.

Rick Strahl
December 02, 2014

# re: Creating multi-target NuGet Packages with vNext

@Daniel - thanks for the link to the analyzer - that'll be useful.

Yeah pretty sure that I had both System.IO and System.IO.FileSystem. But initially I now don't remember whether I just relied on the Intellisense preview or the actual compiler.

I've been seeing really, really weird compilation behavior where a full Rebuild will throw errors, then just a build will work. So maybe I have to rack that up to user error :-) but I could have sworn I had both in there before. It started working today - maybe it was one of the builds of the day that had some weird issue?
 

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2017