Blog series: Migrate a Optimizely/Episerver plugin from CMS 11 to CMS 12 – Packing
In the last blog post of these series we talked about how to solve breaking changes caused by the migration of the plugin for Optimizely CMS 11 which is in the .NET framework 4.8 to .NET Core 5.0 framework. In this blog post, which is a continuation of the previous one, we are going to focus in the code and logic changes required to be able to use our plugin as a NuGet package in a Optimizely CMS 12 project. So without further due, lets begin.
We will begin moving some of the files to places were we can use them in the new package structure. The first file to move is the sitemap plugin controller which was inside a plugin folder. We will now put it at the controllers folder root. Do not forget to change the new namespace inside the class and the view which uses this controller.
We will now move the view that belong to the plugin to the folder Verndale.Sitemap.Robots.Generator.Views\Views\SitemapPlugin\Index.cshtml at the root level of the project. The main reason for this change is that views are going to be compiled as part of the plugin.
Then, we will create a new file called Verndale.Sitemap.Robots.Generator.props inside a new folder called build. The purpose of this file is to copy all the content files to the modules protected folder in the CMS as part of a post build event when the package is installed in another project. This is important to add because NuGet packages in .NET core will not copy what is sent to the content folder anymore. The file content is below:
<Project>
<Target Name="CopyFilesToProject" BeforeTargets="Build">
<Message Text="Copy content files to project" />
<ItemGroup>
<SourceScripts Include="$(MSBuildThisFileDirectory)..\resource\**\*.*"/>
</ItemGroup>
<Copy
SourceFiles="@(SourceScripts)"
DestinationFiles="@(SourceScripts -> '$(MSBuildProjectDirectory)\modules\_protected\%(RecursiveDir)%(Filename)%(Extension)')"
/>
</Target>
</Project>
As next step, we need to modify the base NuGet template file to include the following changes:
- A new schema version for the file, 2013/05
- Change the version of the package
- Add a license type if it was not already added
- Add an icon if it was not already added
- Change the copyright
- Add any dependencies to the dependencies section pointing to the .NET 5.0 version
- Remove any not needed framework reference and include the new ones required for .NET 5.0
- In the files section add the created props file, icons and license and remove any reference to files added to the content folder of the package
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>Verndale.Sitemap.Robots.Generator</id>
<version>1.0.0.8</version>
<title>EPiServer Sitemap Robots Generator</title>
<authors>Verndale</authors>
<owners>Verndale</owners>
<licenseUrl>http://verndale.com</licenseUrl>
<projectUrl>http://verndale.com</projectUrl>
<iconUrl>http://world.episerver.com/PageFiles/3/Icons/Nuget.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Sitemap and robots dynamic generator with gadget and admin plugins for EPiserver</description>
<summary>Sitemap and robots dynamic generator for EPiServer</summary>
<releaseNotes></releaseNotes>
<copyright>Copyright 2020</copyright>
<tags>EPiServer Verndale Sitemap Robots Dynamic Generator</tags>
<frameworkAssemblies>
<frameworkAssembly assemblyName="Microsoft.CSharp" targetFramework=".NETFramework4.7" />
<frameworkAssembly assemblyName="System" targetFramework=".NETFramework4.7" />
<frameworkAssembly assemblyName="System.Configuration" targetFramework=".NETFramework4.7" />
<frameworkAssembly assemblyName="System.Web" targetFramework=".NETFramework4.7" />
<frameworkAssembly assemblyName="System.Xml" targetFramework=".NETFramework4.7" />
</frameworkAssemblies>
</metadata>
<files>
<file src="bin\$configuration$\Verndale.Sitemap.Robots.Generator.dll" target="lib\net45\" />
<file src="bin\$configuration$\Verndale.Sitemap.Robots.Generator.dll" target="lib\net46\" />
<file src="bin\$configuration$\Verndale.Sitemap.Robots.Generator.dll" target="lib\net47\" />
<file src="bin\$configuration$\Verndale.Sitemap.Robots.Generator.dll" target="lib\net48\" />
<file src="bin\$configuration$\Verndale.Sitemap.Robots.Generator.zip" target="content\modules\_protected\Verndale.Sitemap.Robots.Generator\" />
<file src="readme.txt" target="" />
<file src="v-home.png" target="" />
</files>
</package>
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>Verndale.Sitemap.Robots.Generator</id>
<version>5.0.0.10</version>
<title>EPiServer Sitemap Robots Generator</title>
<authors>Verndale</authors>
<owners>Verndale</owners>
<license type="file">license.txt</license>
<projectUrl>http://verndale.com</projectUrl>
<icon>v-home.png</icon>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Sitemap and robots dynamic generator with gadget and admin plugins for EPiserver</description>
<summary>Sitemap and robots dynamic generator for EPiServer</summary>
<releaseNotes></releaseNotes>
<copyright>Copyright 2021</copyright>
<tags>EPiServer Verndale Sitemap Robots Dynamic Generator</tags>
<dependencies>
<group targetFramework="net5.0">
<dependency id="EPiServer.CMS.Core" version="[12.0.3, 13.0.0)" />
<dependency id="EPiServer.CMS.UI" version="[12.0.0, 13.0.0)" />
<dependency id="EPiServer.CMS.AspNetCore" version="[12.0.0, 13.0.0)" />
<dependency id="EPiServer.Framework.AspNetCore" version="[12.0.0, 13.0.0)" />
</group>
</dependencies>
<frameworkReferences>
<group targetFramework="net5.0">
<frameworkReference name="Microsoft.AspNetCore.App" />
</group>
</frameworkReferences>
</metadata>
<files>
<file src="bin\$configuration$\Verndale.Sitemap.Robots.Generator.zip" target="resource\Verndale.Sitemap.Robots.Generator\" />
<file src="bin\$configuration$\net5.0\Verndale.Sitemap.Robots.Generator.dll" target="lib\net5.0\" />
<file src="bin\$configuration$\net5.0\Verndale.Sitemap.Robots.Generator.Views.dll" target="lib\net5.0\" />
<file src="build\Verndale.Sitemap.Robots.Generator.props" target="build" />
<file src="readme.txt" target="" />
<file src="v-home.png" target="" />
<file src="license.txt" target="" />
</files>
</package>
We will then again modify the project file. For this you must right click in your project and the go to the edit project file option
This will open the modified project file, but now we will make the following modifications:
- Project SDK is changed from Microsoft.NET.Sdk to Microsoft.NET.Sdk.Razor
- In any property group add razor support for MVC
- Remove the module.config file from the build
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<WarningLevel>0</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="EPiServer.CMS.Core" Version="12.3.0" />
<PackageReference Include="EPiServer.Framework" Version="12.3.0" />
<PackageReference Include="EPiServer.CMS.UI.Core" Version="12.3.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Features" Version="5.0.13" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
<PackageReference Include="System.Text.Encodings.Web" Version="5.0.1" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<OutputType>Library</OutputType>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<WarningLevel>0</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
</PropertyGroup>
<ItemGroup>
<Content Remove="module.config" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="EPiServer.CMS.Core" Version="12.3.0" />
<PackageReference Include="EPiServer.Framework" Version="12.3.0" />
<PackageReference Include="EPiServer.CMS.UI.Core" Version="12.3.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Features" Version="5.0.13" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
<PackageReference Include="System.Text.Encodings.Web" Version="5.0.1" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project>
The reason we need to enable razor support for MVC and setup the SDK project to Microsoft.NET.Sdk.Razor is because we are using razor syntax in our view. If your views are using the new views syntax, this is not necessary. In addition, if your plugin is using the views compilation, like ours. It is important that you add in the readme instructions and in your Program.cs or Startup file where the plugin is installed, the following line:
services.AddControllersWithViews().AddRazorRuntimeCompilation();
Finally, we will modify the command which will pack the NuGet. We will include anything that the plugin needs like scripts, styles, etc. and then add them to a zip file inside a resource folder in the package. This an script we use to do it all at once.
@echo off
cls
echo Creating the module zip file..
powershell -command "Compress-Archive -Path .\Scripts\, .\Styles\, .\module.config -DestinationPath .\bin\Release\Verndale.Sitemap.Robots.Generator.zip -CompressionLevel Optimal -Force"
echo.
echo Creating NuGet package..
echo.
nuget pack Verndale.Sitemap.Robots.Generator.csproj -Build -Properties Configuration=Release -Verbosity detailed
echo.
echo.
echo Done
Do not forget to compile the code in release mode before executing the script and have the NuGet command line added to your environment variables.
The package should have a similar structure to what you see in the image below.
Inside the lib folder two dlls should appear. One for the code and one for the views.
Inside the resource folder a zip file with all the content like styles and scripts should be there.
The build folder should include the new props file created, which as we explained before, will copy the code inside the resource folder to the protected modules folder after the first compilation of the project when the package is installed.
Content folder should be empty because as we also explained before, the content there will not be copied when the plugin is installed. And any other file like readme, logo, license and nuspec will have the same functionality as they had before.
And that is it. You can now install your new package in a Optimizely CMS 12 project without problems. This is the final blog of these series. If you have any questions or suggestions please let me know in the comments. I hope it can help someone and as always keep learning !!!
Leave a Reply