How to create a NuGet package for a simple App_Plugin style Umbraco v9 package

Posted written by Paul Seal on September 28, 2021 Umbraco

During my #100DaysOfUmbraco challenge I learned how to create packages in Umbraco v9.

So far I have created 2 packages for v9.

Our.Umbraco.UserToDos - A simple to do list user dashboard

and

Portfolio Starter Kit - A starter kit which installs doc types, data types, templates, views, partial views, content and media.

Both of these packages are installed as NuGet packages. In this post I will show you what is required to create a NuGet package out of an AppPlugin type package which doesn't have any .NET code. This post assumes you know how to create an AppPlugin package for Umbraco already, perhaps you are trying to port one over from v7 or v8.

Where to start

Before you can develop Umbraco v9 sites you need to make sure you have downloaded the .NET 5 SDK first.

Install Umbraco v9

Here are some commands you can paste into the command line, it will install umbraco, create a database and install the default Umbraco starter kit for you.

# Ensure we have the latest Umbraco templates
dotnet new -i Umbraco.Templates::9.0.0-rc004

# Create solution/project
dotnet new sln --name MySolution
dotnet new umbraco -n MyProject --friendly-name "Admin User" --email "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="e081848d898ea081848d898ece838f8d">[email&#160;protected]</a>" --password "1234567890" --connection-string "Data Source=|DataDirectory|\Umbraco.sdf;Flush Interval=1" -ce
dotnet sln add MyProject
dotnet add MyProject package Umbraco.TheStarterKit --prerelease

# Run
dotnet run --project MyProject

Run the sites

In the command line you will see a message telling your which url the site is running on.

Copy and paste that Url into your browser, or Ctrl + Click the Url if you are using Windows Terminal.

You can log into umbraco on the site using the login details in the connection string above.

Check that it is running and working.

Add your folder to App_Plugins

Stop the site by pressing Ctrl + C in the command line.

Add your package folder to the App_Plugins folder in the root of the site.

Enter dotnet run in the command line again and when it is running visit the site in the browser.

Check that your package is working how it needs to. It should do if it is a v8 package that doesn't use any .NET code.

Ready to start packaging it up.

Create a new project in the solution which will be named the same as what you want your package to be called. This is the project which will be created as a NuGet package, not the web project.

This project should be created as a Class Library targeting .NET 5

It's a good idea to search NuGet to see if the name of your package is already taken.

Create an App_Plugins folder inside this project and paste in your package folder.

You could use gulp or something to detect changes to these files in the web project and to copy them over.

Your project file for this new project should look like this:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFrameworks>net472;net5.0</TargetFrameworks>
  </PropertyGroup>

  <PropertyGroup>
    <PackageId>Our.Umbraco.UserToDos</PackageId>
    <Version>1.0</Version>

    <PackageIconUrl>https://github.com/prjseal/Our.UmbracoUserToDos/blob/main/images/logo.png?raw=true</PackageIconUrl>
    <PackageLicenseExpression>MIT</PackageLicenseExpression>
    <PackageProjectUrl>https://our.umbraco.com/packages/backoffice-extensions/ourumbracousertodos/</PackageProjectUrl>
    <PackageTags>Umbraco</PackageTags>

    <RepositoryUrl>https://github.com/prjseal/Our.UmbracoUserToDos</RepositoryUrl>

    <Description>A simple to do list user dashboard for Umbraco</Description>
    <PackageReleaseNotes>
      1.0 - First Version
    </PackageReleaseNotes>

    <EmbedUntrackedSources>true</EmbedUntrackedSources>
    <IncludeSymbols>true</IncludeSymbols>
    <SymbolPackageFormat>snupkg</SymbolPackageFormat>

    <ContentTargetFolders>content</ContentTargetFolders>

    <PackageOutputPath>../../output</PackageOutputPath>
    <IncludeBuildOutput>false</IncludeBuildOutput>
  </PropertyGroup>

  <!-- package files -->
  <ItemGroup>
    <Content Include="App_Plugins\**\*.*">
      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
      <CopyToPublishDirectory>Always</CopyToPublishDirectory>
    </Content>

    <!-- target file to copy app_plugins in .netcore -->
    <None Include="build\**\*.*">
      <Pack>True</Pack>
      <PackagePath>buildTransitive</PackagePath>
    </None>

  </ItemGroup>

</Project>

Make sure you change everything to be relevant to your package, don't leave it pointing to the UserToDos package details.

The target frameworks setting means this package can be installed on v8 and v9.

<PropertyGroup>
    <TargetFrameworks>net472;net5.0</TargetFrameworks>
</PropertyGroup

This line tells it not to include a dll.

<IncludeBuildOutput>false</IncludeBuildOutput>

This part tells it to include the App_Plugins folder contents.

<Content Include="App_Plugins\**\*.*">
  <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
  <CopyToPublishDirectory>Always</CopyToPublishDirectory>
</Content>

This part includes a build targets file in the package which contains the rules for copying the files over when the package is installed on v9.

<None Include="build\**\*.*">
  <Pack>True</Pack>
  <PackagePath>buildTransitive</PackagePath>
</None>

Build Targets

In the root of this project create another folder called build. Add a file in the build folder which is the same name as your .csproj file but instead of ending with .csproj change it to .targets

The contents of this file should be like this:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

    <PropertyGroup>
        <UserToDosContentFilesPath>$(MSBuildThisFileDirectory)..\content\App_Plugins\UserToDos\**\*.*</UserToDosContentFilesPath>
    </PropertyGroup>

    <Target Name="CopyUserToDosAssets" BeforeTargets="Build">
        <ItemGroup>
            <UserToDosContentFiles Include="$(UserToDosContentFilesPath)" />
        </ItemGroup>
        <Message Text="Copying UserToDos files: $(UserToDosContentFilesPath) - #@(UserToDosContentFiles->Count()) files"  Importance="high" />
        <Copy
            SourceFiles="@(UserToDosContentFiles)"
            DestinationFiles="@(UserToDosContentFiles->'$(MSBuildProjectDirectory)\App_Plugins\UserToDos\%(RecursiveDir)%(Filename)%(Extension)')"
            SkipUnchangedFiles="true" />

    </Target>

    <Target Name="ClearUserToDosAssets" BeforeTargets="Clean">
        <ItemGroup>
            <UserToDosDir Include="$(MSBuildProjectDirectory)\App_Plugins\UserToDos\" />
        </ItemGroup>
        <Message Text="Clear old UserToDos data"  Importance="high" />
        <RemoveDir Directories="@(UserToDosDir)"  />
    </Target>

</Project>

You can do a find and replace to change UserToDos to whatever the name of package folder is in the App_Plugins folder.

e.g. If the folder for yours is App_Plugins\EditLink\ then you could find and replace UserToDos with EditLink

Now you have everything you need for your package.

You can go to the command line again and run

dotnet pack

This will create a NuGet package for you in the output folder which should be in the folder where your solution file is