Hello again! I have another good news to share with you. WPFSpark.UWP v1.2 is released today.
. You can get the NuGet package from here
. You can view the source code here
. In this blog I will explain in detail how I created the NuGet package for WPFSpark.UWP.
For the past few weeks I have been trying to port the WPFSpark library to the Universal Windows Platform (UWP). Since most of the controls in WPFSpark are already present natively (like ToggleSwitch, Inderminate ProgressBar, ProgressRing to name a few), porting some of the controls would create redundant controls in UWP. So the only control eligible for porting to UWP from WPF was the FluidWrapPanel.
I was able to port the control with minimal changes. The FluidMouseDragBehavior has been renamed to FluidPointerDragBehavior. A reference to the Microsoft.Xaml.Behaviors.Uwp.Managed NuGet package was added for the Behavior class from which FluidPointerDragBehavior is derived.
Ok, now the control has been successfully ported and it’s time to create the NuGet package. However, I wanted to create a NuGet package which contained all the three binaries – x86, x64 & ARM. I did not want to create separate binaries for each and upload them to NuGet. I wanted to have a single NuGet package which, when referenced by a project, would add reference to the appropriate binary based on the platform for which the project is being built.
The search for an apt solution led me here
. I tested it out by creating a sample project, SampleBehaviorLib. Basically, I was using the build
folders to create a NuGet package. (If you want to know more about the directory structure that has to be used to create NuGet packages, see here
). The build
folder contained 3 folders: x86
each of which contained the respective binary. The lib
folder contained the x86
version. The lib
folder will allow the Visual Studio to work properly in Designer mode. The build folder also contained a .targets
file which added the logic for selecting the appropriate binary based on the build platform.
Unfortunately, the above solution did not work for me. When I referenced the NuGet package (created using the above method) in another project, it built successfully for x86 platform. But for x64 and ARM version, the build failed. It gave the following error
1>C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v14.0\AppxPackage\Microsoft.AppXPackage.Targets(1151,5): error APPX1101:Payload contains two or more files with the same destination path 'SampleBehaviorLib.dll'. Source files:
1>C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v14.0\AppxPackage\Microsoft.AppXPackage.Targets(1151,5): error APPX1101: C:\Users\Guest01.nuget\packages\SampleBehaviorLib\1.0.8\lib\uap10.0\SampleBehaviorLib.dll
1>C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v14.0\AppxPackage\Microsoft.AppXPackage.Targets(1151,5): error APPX1101: C:\Users\Guest01.nuget\packages\SampleBehaviorLib\1.0.8\build\uap10.0\x64\SampleBehaviorLib.dll
I visited the NuGet project in GitHub and raised this issue
. The nice folks there helped me out. It seems I had to use the ref
folder instead of the lib
folder. I revisited the NuGet 3 Package Authoring Document
and I found out the following:
Ref folder: ref is a new, optional, directory that contains .NET assemblies defining the public surface (public types and methods) for an application to compile against. The assemblies in this folder may have no implementation, they are purely used to define surface area for the compiler. If the package has no ref directory, then the lib is both the reference assembly and the implementation assembly. It is useful for packages that need to provide a consistent surface area for compilation and intellisense but then have different implementation for different TxMs. [A TxM is the name of a platform that a given asset in a package is supposed to work for. Logically these are an extension of the Target Framework Monikers (TFM) e.g. net45, net46, netcore50, and dnxcore50 are all examples of TxMs. For UWP applications for Windows 10, the TxM is uap10.0.]
Runtimes folder: runtimes is a new, optional, directory that will contain OS specific code, such as CPU architecture and OS specific or otherwise platform-dependent binaries. The runtimes folder contains assemblies and native libraries required to run on specific “runtimes”, which are generally defined by Operating System and CPU architecture. These runtimes are identified using Runtime Identifiers (RIDs) such as win, win-x86, win7-x86, win8-64, etc. For Windows 10 the RIDs are win10-x86, win10-x64 & win10-arm.
So, I have to compile the WPFSpark.UWP project for the three platforms – x86, x64 & ARM and put them in the appropriate folders within the runtimes folder. Next, I have to create an assembly (for WPFSpark.UWP project)which defines the surface area for the compiler and intellisense i.e. an assembly containing only the declarations of the methods and properties defined by the classes within WPFSpark.UWP project (but no implementation). Hmmm, seems like a tedious task. Fortunately, there is a NuGet package which does the job for you – Microsoft.DotNet.BuildTools.GenAPI. This is currently in beta. Therefore when you add the NuGet package (through Manage NuGet Packages option) make sure you check the Include prerelease option so that it is listed in the search results.
This is a very useful package but it does only half the job. According to the documentation of this package
This package provides build-time support generating reference assembly source. Referencing this package will cause the build to produce a CS file in the output directory that represents the public surface area of the project. This package requires .NET Framework 4.6 or later.
I raised an issue in this package’s GitHub forum
to know if there was an easy way to automate the creation of an assembly through a script. It seems there isn’t. I got help from the folks at the NuGet project. They said:
“You can create a project with similar characteristics as your implementation dll and include the source. We didn’t try to create a dll since everyone has a different build system with respect to versioning, signing, etc so we just emit a CS that you can plug into your own project.”
Ok, so now I create a Universal Class Library project in Visual Studio, name it WPFSpark.UWP.Ref and add the generated CS file to it. I build it in Release Mode for the AnyCPU platform. The resulting DLL file will be placed in the reference folder.
So now the WPFSpark.UWP project folder structure looks like this
The WPFSpark.UWP folder contains the source code for the controls. The WPFSpark.Ref folder contains the reference project created for the generated CS file and the WPFSparkUWP.Client folder contains a demo app which references the WPFSpark.UWP NuGet package. The NuGet folder contains the ref and runtimes folder along with nuget.exe and WPFSpark.UWP.nuspec file. The contents of NuGet folder are used to create the WPFSpark.UWP NuGet package.
The NuGet folder structure looks like this
In order to keep the contents of the runtimes folder up-to-date, I have added the following script in the Build Action of the WPFSpark.UWP project
@echo Copying '$(TargetFileName)' to $(ProjectDir)..\Nuget\runtimes\win10-$(PlatformName)\lib\uap10.0
copy /Y $(TargetPath) $(ProjectDir)..\Nuget\runtimes\win10-$(PlatformName)\lib\uap10.0
This will ensure that whenever a build is made for a particular platform (x86/x64/ARM) the output DLL will be copied to the appropriate location in the runtimes folder.
A similar script has been added to the Build Action of the WPFSpark.UWP.Ref project in order to copy the output DLL to the reference folder.
@echo Copying '$(TargetFileName)' to $(ProjectDir)..\Nuget\ref\uap10.0
copy /Y $(TargetPath) $(ProjectDir)..\Nuget\ref\uap10.0
The CreateNuGetPackage.cmd contains the script for creating the WPFSpark.UWP NuGet package.
Hope this helps you create your own NuGet packages containing x86, x84 and ARM binaries.