Migrating From Piranha 7

If you've already create a new web project for .NET Core 3.0 or 3.1 you know that there are some differences between how these versions are configured in Startup.cs. This of course also applies to Piranha, so let's take a look at what you need to do to upgrade your existing application.

Project File and References

When upgrading to netcoreapp3.1 one of the major differences is that all ASP.NET references are now included as Framework References and nuget references ASP.NET should be removed from your project. Let's take a look at the project file for the blog template as an example:

Piranha 7.1 for .NET Core 2.2
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp2.2</TargetFramework>
    <AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
  </PropertyGroup>

  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.4" />
    <PackageReference Include="Piranha" Version="7.1.0" />
    <PackageReference Include="Piranha.AspNetCore" Version="7.1.0" />
    <PackageReference Include="Piranha.AspNetCore.Identity.SQLite" Version="7.1.0" />
    <PackageReference Include="Piranha.AttributeBuilder" Version="7.1.0" />
    <PackageReference Include="Piranha.Data.EF" Version="7.1.0" />
    <PackageReference Include="Piranha.ImageSharp" Version="7.1.0-rc1" />
    <PackageReference Include="Piranha.Local.FileStorage" Version="7.1.0" />
    <PackageReference Include="Piranha.Manager" Version="7.1.0" />
    <PackageReference Include="Piranha.Manager.TinyMCE" Version="7.1.0" />
  </ItemGroup>

</Project>
Piranha 8.0 for .NET Core 3.1
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.0" />
    <PackageReference Include="Piranha" Version="8.0.0" />
    <PackageReference Include="Piranha.AspNetCore" Version="8.0.0" />
    <PackageReference Include="Piranha.AspNetCore.Identity.SQLite" Version="8.0.0" />
    <PackageReference Include="Piranha.AttributeBuilder" Version="8.0.0" />
    <PackageReference Include="Piranha.Data.EF" Version="8.0.0" />
    <PackageReference Include="Piranha.ImageSharp" Version="8.0.0-rc1" />
    <PackageReference Include="Piranha.Local.FileStorage" Version="8.0.0" />
    <PackageReference Include="Piranha.Manager" Version="8.0.0" />
    <PackageReference Include="Piranha.Manager.TinyMCE" Version="8.0.0" />
  </ItemGroup>

</Project>

Configure Services

In ConfigureServices the main difference is how the ASP.NET services are added. AddMvc has now been split into separate methods for Razor Pages and MVC.

Piranha 7.1 for .NET Core 2.2
public void ConfigureServices(IServiceCollection services)
{
    services.AddLocalization(options =>
        options.ResourcesPath = "Resources"
    );
    services.AddMvc()
        .AddPiranhaManagerOptions()
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    services.AddPiranha();
    services.AddPiranhaApplication();
    services.AddPiranhaFileStorage();
    services.AddPiranhaImageSharp();
    services.AddPiranhaManager();
    services.AddPiranhaTinyMCE();
    services.AddPiranhaMemoryCache();

    services.AddPiranhaEF(options =>
        options.UseSqlite(Configuration.GetConnectionString("piranha")));
    services.AddPiranhaIdentityWithSeed<IdentitySQLiteDb>(options =>
        options.UseSqlite(Configuration.GetConnectionString("piranha")));
}
Piranha 8.0 for .NET Core 3.0
public void ConfigureServices(IServiceCollection services)
{
    services.AddLocalization(options =>
        options.ResourcesPath = "Resources"
    );
    services.AddControllersWithViews();
    services.AddRazorPages()
        .AddPiranhaManagerOptions();

    services.AddPiranha();
    services.AddPiranhaApplication();
    services.AddPiranhaFileStorage();
    services.AddPiranhaImageSharp();
    services.AddPiranhaManager();
    services.AddPiranhaTinyMCE();
    services.AddPiranhaMemoryCache();

    services.AddPiranhaEF(options =>
        options.UseSqlite(Configuration.GetConnectionString("piranha")));
    services.AddPiranhaIdentityWithSeed<IdentitySQLiteDb>(options =>
        options.UseSqlite(Configuration.GetConnectionString("piranha")));
}

Configure

Again UseMvc has been split up into a number of components that needs to be registered differenctly. Pay attention to the order of the middleware, and especially to the position of the Authorization and Authentication components.

Piranha 7.1 for .NET Core 2.2
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApi api)
{
    // Initialize Piranha
    App.Init(api);

    // Configure cache level
    App.CacheLevel = Piranha.Cache.CacheLevel.Basic;

    // Build content types
    var pageTypeBuilder = new Piranha.AttributeBuilder.PageTypeBuilder(api)
        .AddType(typeof(Models.BlogArchive))
        .AddType(typeof(Models.StandardPage));
    pageTypeBuilder.Build()
        .DeleteOrphans();
    var postTypeBuilder = new Piranha.AttributeBuilder.PostTypeBuilder(api)
        .AddType(typeof(Models.BlogPost));
    postTypeBuilder.Build()
        .DeleteOrphans();

    // Configure Tiny MCE
    EditorConfig.FromFile("editorconfig.json");

    // Register middleware
    app.UseStaticFiles();
    app.UseAuthentication();
    app.UsePiranha();
    app.UsePiranhaManager();
    app.UsePiranhaTinyMCE();
    app.UseMvc(routes =>
    {
        routes.MapRoute(name: "areaRoute",
        template: "{area:exists}/{controller}/{action}/{id?}",
        defaults: new { controller = "Home", action = "Index" });

        routes.MapRoute(
        name: "default",
        template: "{controller=home}/{action=index}/{id?}");
    });
}
Piranha 8.0 for .NET Core 3.1
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApi api)
{
    // Initialize Piranha
    App.Init(api);

    // Configure cache level
    App.CacheLevel = Piranha.Cache.CacheLevel.Basic;

    // Build content types
    var pageTypeBuilder = new Piranha.AttributeBuilder.PageTypeBuilder(api)
        .AddType(typeof(Models.BlogArchive))
        .AddType(typeof(Models.StandardPage));
    pageTypeBuilder.Build()
        .DeleteOrphans();
    var postTypeBuilder = new Piranha.AttributeBuilder.PostTypeBuilder(api)
        .AddType(typeof(Models.BlogPost));
    postTypeBuilder.Build()
        .DeleteOrphans();

    // Configure Tiny MCE
    EditorConfig.FromFile("editorconfig.json");

    // Register middleware
    app.UseStaticFiles();
    app.UsePiranha();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UsePiranhaIdentity();
    app.UsePiranhaManager();
    app.UsePiranhaTinyMCE();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");

        endpoints.MapPiranhaManager();
    });
}

Introducing Simple Setup

To help migration to this version, and future version we've decided a more simple way to setup Piranha if you're building an integrated web application (i.e. a traditional application with a server-generated client). This setup method abstracts the gritty details of setting up the application and does this for you. Let's again take a look at how setup for the Blog template would look like.

ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
    services.AddPiranha(options =>
    {
        options.UseFileStorage();
        options.UseImageSharp();
        options.UseManager();
        options.UseTinyMCE();
        options.UseMemoryCache();
        options.UseEF(db =>
            db.UseSqlite(Configuration.GetConnectionString("piranha")));
        options.UseIdentityWithSeed<IdentitySQLiteDb>(db =>
            db.UseSqlite(Configuration.GetConnectionString("piranha")));
    });
}
Configure

Besides packaging all of the ASP.NET setup we've also added a new ContentTypeBuilder that adds all Content Types in the specified Assemblies for you. You can even add multiple assemblies by add several calls to AddAssembl

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApi api)
{
    // Initialize Piranha
    App.Init(api);

    // Configure cache level
    App.CacheLevel = Piranha.Cache.CacheLevel.Basic;

    // Build content types
    new ContentTypeBuilder(api)
        .AddAssembly(typeof(Startup).Assembly)
        .Build()
        .DeleteOrphans();

    // Configure Tiny MCE
    EditorConfig.FromFile("editorconfig.json");

    // Middleware setup
    app.UsePiranha(options => 
    {
        options.UseManager();
        options.UseTinyMCE();
        options.UseIdentity();
    });
}

Integrated Middleware

We've also added a new Integrated Middleware component that is enabled when using Simple Setup. This middleware component replaces all of the existing Piranha middleware components and processes the entire request in one pass. The result is better performance and more failsafe routing behavior for the application.

This new middleware can be used without Simple Setup as well by replacing the following line:

Standard Pipeline
app.UsePiranha();
Integrated Pipeline
app.UseIntegratedPiranha

Archive Pages

In our constant efforts to simplify things we will gradually remove the type ArchivePage<T> which has been marked as obsolete from this version. Archive Pages should now inherit from the same base class as regular pages and is instead marked as Archives with the PageTypeAttribute.

If you want a PostArchive Property on your Page just like before you add this manually, for example.

Archive Page in 7.1
[PageType(Title = "Blog archive", UseBlocks = false)]
public class BlogArchive : ArchivePage<BlogArchive>
{
}
Archive Page in 8.0
[PageType(Title = "Blog archive", UseBlocks = false, IsArchive = true)]
public class BlogArchive : Page<BlogArchive>
{
    public PostArchive<DynamicPost> Archive { get; set; }
}

The reason for this is simply that the Archive is not populated when getting the Page model, and we want to give you the flexibility on how you handle your data model.

Wrapping Up

That's it folks, you can read more what is included in detail over at GitHub by reading the Changelog. We truly hope you enjoy this new version of Piranha CMS and continue to use it, because we have lot's of exiting new features coming up in the next releases!

Over and out!