Blog{tacular} Triptacular logo

Check out some of the places, events, other news and technologies that we occasionally write about.

Could not resolve Piranha.IApi

A snippet of the Piranha CMS Homepage

An odd runtime, Piranha CMS related error due to mismatched Entity Framework libraries.

Piranha CMS

Piranha is an open source, headless content management system powered by ASP.NET Core. It makes a great engine for blogs by storing and providing structured content on demand via its API. Read more about the Piranha CMS at their website or via Github.

The Issue

Several months ago, I had created a website using Piranha's out of the box project templates for ASP.NET Core. Of course, life happened, and the project sat dormant for some time. When I came back to the project months later, I updated all of the project's NuGet dependencies except for the Pirhana packages. I left those for last. Each time I updated a set of related packages (i.e. ASP.NET Core related libs, Entity Framework libs, etc.) I tested out the site expecting to see one thing or another broken (just my luck 😊). At each step, I was happy to find nothing was broken and the site worked as expected. Until, that is, I upgraded the Piranha packages from generation 8.3 to 8.4.

update-piranha-cms-packages-in-visual-studio.png

To be honest I wasn't feeling too bad about things, until I launched the app and saw this nastiness at the top of the stack trace.

PM> dotnet run
[10:09:25 FTL] Application startup exception
System.Exception: Could not resolve a service of type 'Piranha.IApi' for the parameter 'api' of method 'Configure' on type 'FooApp.Web.Startup'.
 ---> System.MissingMethodException: Method not found: 'Microsoft.EntityFrameworkCore.Migrations.Operations.Builders.OperationBuilder`1<Microsoft.EntityFrameworkCore.Migrations.Operations.AddColumnOperation> Microsoft.EntityFrameworkCore.Migrations.Operations.Builders.ColumnsBuilder.Column(System.String, System.Nullable`1<Boolean>, System.Nullable`1<Int32>, Boolean, System.String, Boolean, System.Object, System.String, System.String, System.Nullable`1<Boolean>, System.String)'.
   at Piranha.Data.EF.SQLServer.Migrations.InitialCreate.<>c.<Up>b__0_0(ColumnsBuilder table)
   at Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder.CreateTable[TColumns](String name, Func`2 columns, String schema, Action`1 constraints, String comment)
   at Piranha.Data.EF.SQLServer.Migrations.InitialCreate.Up(MigrationBuilder migrationBuilder)
   at Microsoft.EntityFrameworkCore.Migrations.Migration.BuildOperations(Action`1 buildAction)
   at Microsoft.EntityFrameworkCore.Migrations.Migration.get_UpOperations()
   at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.GenerateUpSql(Migration migration, MigrationsSqlGenerationOptions options)
   at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.<>c__DisplayClass16_2.<GetMigrationCommandLists>b__2()
   at Microsoft.EntityFrameworkCore.Migrations.Internal.Migrator.Migrate(String targetMigration)
   at Microsoft.EntityFrameworkCore.RelationalDatabaseFacadeExtensions.Migrate(DatabaseFacade databaseFacade)
   at Piranha.Db`1..ctor(DbContextOptions`1 options)
   at Piranha.Data.EF.SQLServer.SQLServerDb..ctor(DbContextOptions`1 options)

Red Herring? ... Or Maybe I'm Just Slow

The error above is complaining about this snippet of code from my app. It's claiming that it can't find the argument to IApi to the Configure method in Startup.cs.

public void Configure(
	IApplicationBuilder app,
	IWebHostEnvironment env, 
	IApi api)

What on earth? It turns out, a missing IApi variable is often related to database issues. As shown below, this specific case was triggered by Piranha not being able to run a series of EF migrations.

Online Answers

search-for-missing-piranha-iapi-variable.png

Naturally, I looked for a solution to this issue and found a few ideas. Most of the information I found revolved around an inability for Piranha to connect to the underlying database. I double checked connection strings, found they were solid and kept troubleshooting.

What's Actually Happening

Looking at the stack trace more closely, I noticed the following entries leading to a System.MissingMethodException.

System.MissingMethodException: Method not found: 'Microsoft.EntityFrameworkCore.Migrations.Operations.Builders.OperationBuilder`1<Microsoft.EntityFrameworkCore.Migrations.Operations.AddColumnOperation> Microsoft.EntityFrameworkCore.Migrations.Operations.Builders.ColumnsBuilder.Column(System.String, System.Nullable`1<Boolean>, System.Nullable`1<Int32>, Boolean, System.String, Boolean, System.Object, System.String, System.String, System.Nullable`1<Boolean>, System.String)'.
   at Piranha.Data.EF.SQLServer.Migrations.InitialCreate.<>c.<Up>b__0_0(ColumnsBuilder table)
   at Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder.CreateTable[TColumns](String name, Func`2 columns, String schema, Action`1 constraints, String comment)
   at Piranha.Data.EF.SQLServer.Migrations.InitialCreate.Up(MigrationBuilder migrationBuilder)

Apparently, as part of the upgrade from gen 8.3 to 8.4 Piranha requires a few database changes that are trying to be invoked via Entity Framework Migrations. And it seems that, OperationBuilder<TOperation> can't be found by the runtime.

But why?

So, after a little bit of digging I saw the OperationsBuilder<TOperation> class in question was part of the Microsoft.EntityFrameworkCore.Relational assembly. And what's odd is that this assembly is included in the application build as a transitive dependency.

listing-of-entity-framework-dependencies.jpg

It occurred to me that I might have a different version of this assembly than Piranha is expecting. Sure enough, after fetching the source code to Piranha's 8.3 product and loading it into Visual Studio, I came across this.

piranha-source-code-in-visual-studio-show-assembly-reference.jpg

You can also more easily find the EF version number Piranha is using by querying the __EFMigrationsHistory table and examining the ProductVersion column. I guess the moral of the story is, stick with the version of EF Piranha CMS uses if you want a trouble free experience.

A Crude Workaround

I can be stubborn sometimes, and didn't really feel like giving up on the latest version of Entity Framework and friends. I wish I could tell you there was a really good reason behind this thinking, but to be completely honest, I'm not even sure what the feature set of EF Core gen 5 offers above gen 3.1. Still, I had a hunch that if I could keep Piranha's EF Context from trying to invoke database migrations the app would probably work.

Script-Migration ⬅️ 👏

In the end, I created a new project using Piranha's Template and instead of running the project, issued Script-Migration in Visual Studio's Package Manager Console.

PM> Script-Migration

The output of Script-Migration is a set of SQL (in this case TSQL) statements. A comparison of my existing database's __EFMigrationsHistory table and the full output of Script-Manager indicated that I only needed to issue the following statements to make my database compatible with Piranha 8.4.

ALTER TABLE [Piranha_Sites] ADD [LogoId] uniqueidentifier NULL;
GO
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20200625060542_AddSiteLogo', N'3.1.0');
GO
ALTER TABLE [Piranha_Posts] ADD [MetaTitle] nvarchar(128) NULL;
GO
ALTER TABLE [Piranha_Posts] ADD [OgDescription] nvarchar(256) NULL;
GO
ALTER TABLE [Piranha_Posts] ADD [OgImageId] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000';
GO
ALTER TABLE [Piranha_Posts] ADD [OgTitle] nvarchar(128) NULL;
GO
ALTER TABLE [Piranha_Pages] ADD [MetaTitle] nvarchar(128) NULL;
GO
ALTER TABLE [Piranha_Pages] ADD [OgDescription] nvarchar(256) NULL;
GO
ALTER TABLE [Piranha_Pages] ADD [OgImageId] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000';
GO
ALTER TABLE [Piranha_Pages] ADD [OgTitle] nvarchar(128) NULL;
GO
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20200812110105_AddOgFields', N'3.1.0');
GO

After issuing the statements above, I was able to run the app without incident.

Leave a comment

Please note that we won't show your email to others, or use it for sending unwanted emails. We will only use it to render your Gravatar image and to validate you as a real person.