Do you have ever asked yourself how to create a NuGet package for your awesome components or services you’ve created? If so you don’t have to look further! In this post, I will show you how you can create a template project so you can create your own NuGets for Xamarin.Forms! 💪
James Montemagno has already created an extension for Visual Studio (only available for Windows) with which you can create a NuGet for xamarin.forms. However, when I’m trying to use this extension visual studio stop working and do not respond at all. So if you do have the same problem with it or just want to learn how to create a template project by your own, just keep reading 😉
If you don’t want to know how to create a project template and just want to use it, you can find the source code here and the finished template here. Just clone or download it and add the zip file to the templates folder located in C:\\Users\Username\Documents\Visual Studio X\Templates\.
First things first, I know you’re eager to start coding but at first, we should start with some basic theory about NuGets.
What is a NuGet
As a developer, you need some kind of mechanism through which you can share or consume code. This is especially helpful if you don’t want to write duplicate code or have to reinvent things other developers have already done.
To reuse code, you don’t want to have to include individual code files (compiled as .DLL), dependencies or other resources separately. So all these files will be bundled to a single package that can be easily shared.
For .NET the Microsoft-supported mechanism to share such packages is called NuGet. A NuGet defines how these packages for .NET are created, hosted and consumed.
Nuget for Xamarin.Forms
A NuGet package for Xamarin.Forms is a bit different than a default .netstandard NuGet package. This is because of default Xamarin.Forms project consists of at least three projects, each of which addresses a different platform. This means that our NuGet has to support at least each of these three (.netstandard, ios, android) platforms to be compatible with our Xamarin.Forms project.
Creating a project template
To create a NuGet that is compatible with multiple frameworks, we need to modify the .csproj file. To not always have to change the project file we’re going to create a template for a Xamarin.Froms NuGet project.
Create a new Project
So finally let’s get started!
To create our NuGet template fire up Visual Studio and create a new .NETStandard class library.
First, we delete the Class1 file that is created by default.
Then we are going to edit the csproj file. To do so right-click the project folder and select “Edit Project File”.
The .csproj file tells the compiler for what platform the project should be built and which files should be included. It also specifies SDK that should be used for the project and defines the metadata. If you want to learn more about the project file and it’s structure take a look at the official Microsoft site.
Add multiple frameworks
The first thing we need to do to support multiple targets flawlessly is to change the current SDK of the project. To do so you have to change the project SDK to MSBuild.Sdk.Extras/2.0.41
<Project Sdk="MSBuild.Sdk.Extras/2.0.41"> ... </Project>
If you have changed the SDK version we can continue to modify the property group section. This section specifies which frameworks the project will be built for and defines the metadata. Because we are going to create a template we have to be careful which metadata we are going to hardcode. Fortunately, Microsoft offers a mechanism called build parameters so we don’t have to hardcode these critical values. These parameters will be replaced by the actual values when you create a new project using this template. To get a list of all params that are available take a look at the Microsoft site.
That’s how our finished property group will look like:
<Project Sdk="MSBuild.Sdk.Extras/2.0.41"> <PropertyGroup> <TargetFrameworks>xamarin.ios10;monoandroid90;netstandard2.0</TargetFrameworks> <!-- other frameworks you can use--> <!--xamarin.mac20;xamarin.tvos10;xamarin.watchos10;monoandroid10.0;tizen40;netcoreapp2.1;netcoreapp3.1;net461--> <!-- Set project metadata --> <Company>FILL IN</Company> <Copyright>Copyright © FILL IN</Copyright> <RepositoryUrl>FILL IN</RepositoryUrl> <Authors>FILL IN</Authors> <Owners>FILL IN</Owners> <PackageReleaseNotes /> <PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance> <PublishRepositoryUrl>true</PublishRepositoryUrl> <RepositoryType>git</RepositoryType> <Product>$safeprojectname$ (netstandard2.0)</Product> <NeutralLanguage>en</NeutralLanguage> <Platform>AnyCPU</Platform> <DebugType>portable</DebugType> <PackageId>$safeprojectname$</PackageId> <AssemblyName>$safeprojectname$</AssemblyName> <RootNamespace>$safeprojectname$</RootNamespace> <EnableDefaultCompileItems>false</EnableDefaultCompileItems> <DebugType>portable</DebugType> <Version>1.0.0</Version> </PropertyGroup> </Project>
What exactly happened in the code above?
The most important change is the renaming of the first node in the property group. To support multiple frameworks we need to change the node from TargetFramework to TargetFrameworks. The value of this node specifies all frameworks we are going to support. For a default Xamarin.Forms application we should support at least Xamarin.iOS, MonoAndroid and .NETStandard (you have to provide a minimum supported version for every framework).
To add additional frameworks just add them to the value of this node
The remaining properties specify the metadata of the project. You may have noticed the $safeprojectname$ value set for a bunch of properties. This is one of the built parameters I mentioned earlier. It sets the values of these nodes to the project name the user enters when creating a new project. Every node with “FILL IN” as value is just a placeholder and the user has to config these values later in the project settings.
Property groups with conditions
In Addition to our main property group, we will add a property group for the Debug and Release config. With these two different property groups, we can decide what happens when the package is built with debug and release configuration.
Just add this to your actual .csproj file:
... <!-- If config is debug create debug symbols --> <PropertyGroup Condition=" '$(Configuration)'=='Debug' "> <DebugSymbols>true</DebugSymbols> </PropertyGroup> <!-- If config is release create a package --> <PropertyGroup Condition=" '$(Configuration)'=='Release' "> <GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GenerateDocumentationFile>true</GenerateDocumentationFile> <!-- sourcelink: Include PDB in the built .nupkg --> <AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder> </PropertyGroup> ...
With the above code, we tell the project that when we built in Debug mode it should also generate the debug symbols. When we build in Release mode we also want to generate a package (NuGet) and some documentation files for the project.
If you don’t want to include your .pdb files in the NuGet package you can remove the AllowedOutputExtensionsInPackageBuildOutputFolder node.
ItemGroups – Configuration for the different platforms
To build the NuGet for different platforms we have to tell the project which files are for which platform. Similar to a Xamarin.Forms application we will probably create platform-dependent files (like custom renderer). So we have to take care that the builder knows which files are meant for which platform. To do this we can create different ItemGroups. Each group will take care of a specific platform. Besides the different platforms (iOS, Android, …) we also have a shared code basis that is included in every platform.
So we will need to configure a default ItemGroup (shared project) and an ItemGroup for each platform we are going to support.
The code for the individual item groups will look like this:
... <!-- Create the item groups for the different platforms --> <ItemGroup> <!-- Include a folder for each platform --> <Folder Include="ios\" /> <Folder Include="android\" /> <Folder Include="standard\" /> <Folder Include="shared\" /> <Compile Include="shared\**\*.cs" /> <None Include="readme.txt" pack="true" PackagePath="." /> <PackageReference Include="Xamarin.Forms" Version="126.96.36.1991265" /> </ItemGroup> <ItemGroup Condition=" $(TargetFramework.StartsWith('netstandard')) "> <Compile Include="standard\**\*.standard.cs" /> </ItemGroup> <ItemGroup Condition=" $(TargetFramework.StartsWith('xamarin.ios')) "> <Compile Include="ios\**\*.ios.cs" /> </ItemGroup> <ItemGroup Condition=" $(TargetFramework.StartsWith('monoandroid')) "> <Compile Include="android\**\*.android.cs" /> </ItemGroup> <ItemGroup Condition=" $(TargetFramework.StartsWith('uwp')) "> <Compile Include="uap\**\*.uwp.cs" /> </ItemGroup> ...
In the default item group, we will include all folders for the different platforms. These folders are not necessary but help us to structure the code and keep a better overview of the different platform implementations.
The structure of the code will look like the following:
So there is a separate folder for each platform where the platform implementations will be stored. As an additional readability feature, every file (except the shared files) will contain the platform in their names e.g. ScreenSize.ios.cs. This is enormously helpful when you have opened multiple files for a specific implementation and different platforms.
The task of the different ItemGroups is to include the correct files (the ones in the platform-specific folders) into the correct builds
Adding some additional item groups
If you want to create a template that supports virtually all platforms you can add this code:
... <!-- The item groups for the other framworks--> <!-- <ItemGroup Condition=" $(TargetFramework.StartsWith('Xamarin.TVOS')) "> <Compile Include="tv\**\*.tv.cs" /> </ItemGroup>--> <!-- <ItemGroup Condition=" $(TargetFramework.StartsWith('Xamarin.WatchOS')) "> <Compile Include="watch\**\*.watch.cs" /> </ItemGroup> --> <!-- <ItemGroup Condition=" $(TargetFramework.StartsWith('Xamarin.Mac')) "> <Compile Include="mac\**\*.mac.cs" /> </ItemGroup> --> <!-- <ItemGroup Condition=" $(TargetFramework.StartsWith('netcoreapp')) "> <Compile Include="core\**\*.core.cs" /> </ItemGroup> --> <!-- <ItemGroup Condition=" $(TargetFramework.StartsWith('Tizen')) "> <Compile Include="tizen\**\*.tizen.cs" /> <PackageReference Include="Tizen.NET" Version="4.0.0" /> </ItemGroup> --> ...
Now you just have to add the new framework you want to support to the TargetFrameworks and uncomment the corresponding ItemGroup.
Create a Template
So, now we are ready to create a template from our project. This is super easy in visual studio!
- In the configured project click Project (menu bar) -> Export Template…
- In the Export Wizard select “Project Template” and click Next.
- Enter a name and a description for the template
- (optional) If you want you can provide an icon and image for your template
- Click Finish
Visual Studio 2019 and custom templates
If you are using visual studio 2019 and also have problems with it to find your template it’s likely because of the flat project hierarchy change from vs17 to vs19.
Modify the *.vstemplate file
To solve this issue you have to add some additional properties to your .vstmplate file.
- Go to C:\Users\Username\Documents\Visual Studio 2019\My Exported Templates
- Open your template *.zip
- Edit the .vstemplate file inside the zip
- Add the following lines inside the TemplateData node
<LanguageTag>CSharp</LanguageTag> <PlatformTag>all</PlatformTag> <ProjectTypeTag>mobile</ProjectTypeTag>
- Save the file
- Copy the zip and replace the one under C:\Users\Username\Documents\Visual Studio 2019\Templates\ProjectTemplates\
- Restart visual studio
- Now visual studio should be able to find your template
Use your template!
Congrats, you have created your own visual studio project template! 🎉
Now you just have to restart visual studio and you should see your template.
You can find the entire code on GitHub.