OrchardCMS / OrchardCore

Orchard Core is an open-source modular and multi-tenant application framework built with ASP.NET Core, and a content management system (CMS) built on top of that framework.
https://orchardcore.net
BSD 3-Clause "New" or "Revised" License
7.43k stars 2.4k forks source link

Orchard throws with a Module containing migrations #7558

Closed juchom closed 4 years ago

juchom commented 4 years ago

I'm hitting an issue with this scenario.

Create a VS solution with two projects: ASP.Net Empty project -> MyWebsite.Backend Class Library (.net core) -> MyWebsite.Backend.BaseModule

Here is MyWebsite.Backend files:

MyWebsite.Backend.csproj

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

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

  <ItemGroup>
    <PackageReference Include="OrchardCore.Application.Cms.Core.Targets" Version="1.0.0-rc2-15413" />
    <PackageReference Include="OrchardCore.Logging.Serilog" Version="1.0.0-rc2-15413" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\MyWebsite.Backend.BaseModule\MyWebsite.Backend.BaseModule.csproj" />
  </ItemGroup>

</Project>

Program.cs

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;

namespace MyWebsite.Backend
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureLogging(logging => logging.ClearProviders())
                .UseSerilog((hostingContext, configBuilder) =>
                {
                    configBuilder.ReadFrom.Configuration(hostingContext.Configuration).Enrich.FromLogContext();
                })
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OrchardCore.Logging;

namespace MyWebsite.Backend
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddOrchardCms();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseOrchardCore(c => c.UseSerilogTenantNameLogging());
        }
    }
}

Add a folder Recipes and the following file mywebsite.recipe.json

{
  "name": "MyWebsite",
  "displayName": "MyWebsite",
  "description": "MyWebsite initial setup",
  "author": "MyWebsite",
  "website": "",
  "version": "",
  "issetuprecipe": true,
  "categories": [],
  "tags": [],
  "steps": [
    {
      "name": "Feature",
      "enable": [
        "Application.Default",
        "MyWebsite.Backend",
        "MyWebsite.Backend.BaseModule",
        "OrchardCore.Settings",
        "OrchardCore.Admin",
        "OrchardCore.Contents",
        "OrchardCore.ShortCodes",
        "OrchardCore.Liquid",
        "OrchardCore.Templates",
        "OrchardCore.ContentTypes",
        "OrchardCore.Alias",
        "OrchardCore.Autoroute",
        "OrchardCore.ContentFields",
        "OrchardCore.ContentPreview",
        "OrchardCore.Contents.FileContentDefinition",
        "OrchardCore.CustomSettings",
        "OrchardCore.Deployment",
        "OrchardCore.Deployment.Remote",
        "OrchardCore.Diagnostics",
        "OrchardCore.DynamicCache",
        "OrchardCore.Resources",
        "OrchardCore.Widgets",
        "OrchardCore.Features",
        "OrchardCore.Feeds",
        "OrchardCore.Flows",
        "OrchardCore.HomeRoute",
        "OrchardCore.Html",
        "OrchardCore.Indexing",
        "OrchardCore.Scripting",
        "OrchardCore.Layers",
        "OrchardCore.Lists",
        "OrchardCore.Lucene",
        "OrchardCore.Markdown",
        "OrchardCore.Media",
        "OrchardCore.Title",
        "OrchardCore.Menu",
        "OrchardCore.Navigation",
        "OrchardCore.Queries",
        "OrchardCore.Users",
        "OrchardCore.Recipes",
        "OrchardCore.Roles",
        "OrchardCore.Themes",
        "TheAdmin"
      ],
      "disable": [
        "OrchardCore.AdminMenu",
        "OrchardCore.AdminTemplates",
        "OrchardCore.Apis.GraphQL",
        "OrchardCore.BackgroundTasks",
        "OrchardCore.ContentFields.Indexing.SQL",
        "OrchardCore.Localization",
        "OrchardCore.ContentLocalization",
        "OrchardCore.ContentLocalization.ContentCulturePicker",
        "OrchardCore.Sitemaps",
        "OrchardCore.ContentLocalization.Sitemaps",
        "OrchardCore.DataProtection.Azure",
        "OrchardCore.Email",
        "OrchardCore.Facebook",
        "OrchardCore.Facebook.Login",
        "OrchardCore.Facebook.Widgets",
        "OrchardCore.Forms",
        "OrchardCore.GitHub.Authentication",
        "OrchardCore.Google.Analytics",
        "OrchardCore.Google.GoogleAuthentication",
        "OrchardCore.HealthChecks",
        "OrchardCore.Https",
        "OrchardCore.Lucene.ContentPicker",
        "OrchardCore.Lucene.Worker",
        "OrchardCore.Media.Cache",
        "OrchardCore.Media.Azure.Storage",
        "OrchardCore.Microsoft.Authentication.AzureAD",
        "OrchardCore.Microsoft.Authentication.MicrosoftAccount",
        "OrchardCore.MiniProfiler",
        "OrchardCore.OpenId",
        "OrchardCore.OpenId.Client",
        "OrchardCore.OpenId.Management",
        "OrchardCore.OpenId.Server",
        "OrchardCore.OpenId.Validation",
        "OrchardCore.PublishLater",
        "OrchardCore.Queries.Sql",
        "OrchardCore.ReCaptcha",
        "OrchardCore.ReCaptcha.Users",
        "OrchardCore.XmlRpc",
        "OrchardCore.RemotePublishing",
        "OrchardCore.ResponseCompression",
        "OrchardCore.ReverseProxy",
        "OrchardCore.Setup",
        "OrchardCore.Sitemaps.RazorPages",
        "OrchardCore.Taxonomies",
        "OrchardCore.Tenants",
        "OrchardCore.Tenants.FileProvider",
        "OrchardCore.Twitter",
        "OrchardCore.Twitter.Signin",
        "OrchardCore.Users.ChangeEmail",
        "OrchardCore.Users.Registration",
        "OrchardCore.Users.ResetPassword",
        "OrchardCore.Users.TimeZone",
        "OrchardCore.Workflows",
        "OrchardCore.Workflows.Http",
        "OrchardCore.Workflows.Timers"
      ]
    },
    {
      "name": "Themes",
      "Site": null,
      "Admin": "TheAdmin"
    }
  ]
}

In BaseModule project

MyWebsite.Backend.BaseModule.csproj

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

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

  <ItemGroup>
    <PackageReference Include="OrchardCore.ContentManagement.Abstractions" Version="1.0.0-rc2-15413" />
    <PackageReference Include="OrchardCore.Infrastructure.Abstractions" Version="1.0.0-rc2-15413" />
    <PackageReference Include="OrchardCore.Module.Targets" Version="1.0.0-rc2-15413" />
    <PackageReference Include="OrchardCore.Recipes.Abstractions" Version="1.0.0-rc2-15413" />
  </ItemGroup>
</Project>

Manifest.cs

using OrchardCore.Modules.Manifest;

[assembly: Module(
    Name = "MyWebsite Base Module",
    Author = "MyWebsite",
    Website = "",
    Version = "0.1",
    Description = "MyWebsite Base Module",
    Category = "MyWebsite"
)]

STOP HERE: and run the project, everything works fine and we can see that our module is enabled.

Now add this code to the BaseModule project

Startup.cs

using MyWebsite.Backend.BaseModule.Migrations;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.Data.Migration;
using OrchardCore.Modules;

namespace MyWebsite.Backend.BaseModule
{
    public class Startup : StartupBase
    {
        public override void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IDataMigration, BaseMigrator>();
        }
    }
}

Add a folder Migrations

add in this folder

Migrator.cs

using OrchardCore.ContentManagement.Metadata;
using OrchardCore.Data.Migration;
using OrchardCore.Recipes.Services;

namespace MyWebsite.Backend.BaseModule.Migrations
{
    public partial class BaseMigrator : DataMigration
    {
        private readonly IRecipeMigrator _recipeMigrator;
        private readonly IContentDefinitionManager _contentDefinitionManager;

        public BaseMigrator(IRecipeMigrator recipeMigrator, IContentDefinitionManager contentDefinitionManager)
        {
            _recipeMigrator = recipeMigrator;
            _contentDefinitionManager = contentDefinitionManager;
        }

        public async Task<int> CreateAsync()
        {
            await _recipeMigrator.ExecuteAsync("0001_enableauthfeatures.json", this);

            return 1;
        }
    }
}

0001_enableauthfeatures.json (we enable OrchardCore.Microsoft.Authentication.AzureAD & OrchardCore.Users.Registration)

{
  "steps": [
    {
      "name": "Feature",
      "enable": [
        "Application.Default",
        "MyWebsite.Backend",
        "MyWebsite.Backend.BaseModule",
        "OrchardCore.Settings",
        "OrchardCore.Admin",
        "OrchardCore.Contents",
        "OrchardCore.ShortCodes",
        "OrchardCore.Liquid",
        "OrchardCore.Templates",
        "OrchardCore.ContentTypes",
        "OrchardCore.Alias",
        "OrchardCore.Autoroute",
        "OrchardCore.ContentFields",
        "OrchardCore.ContentPreview",
        "OrchardCore.Contents.FileContentDefinition",
        "OrchardCore.CustomSettings",
        "OrchardCore.Deployment",
        "OrchardCore.Deployment.Remote",
        "OrchardCore.Diagnostics",
        "OrchardCore.DynamicCache",
        "OrchardCore.Resources",
        "OrchardCore.Email",
        "OrchardCore.Widgets",
        "OrchardCore.Features",
        "OrchardCore.Feeds",
        "OrchardCore.Flows",
        "OrchardCore.HomeRoute",
        "OrchardCore.Html",
        "OrchardCore.Indexing",
        "OrchardCore.Scripting",
        "OrchardCore.Layers",
        "OrchardCore.Lists",
        "OrchardCore.Lucene",
        "OrchardCore.Markdown",
        "OrchardCore.Media",
        "OrchardCore.Title",
        "OrchardCore.Menu",
        "OrchardCore.Microsoft.Authentication.AzureAD",
        "OrchardCore.Navigation",
        "OrchardCore.Queries",
        "OrchardCore.Users",
        "OrchardCore.Recipes",
        "OrchardCore.Roles",
        "OrchardCore.Themes",
        "OrchardCore.Users.Registration",
        "TheAdmin"
      ],
      "disable": [
        "OrchardCore.AdminMenu",
        "OrchardCore.AdminTemplates",
        "OrchardCore.Apis.GraphQL",
        "OrchardCore.BackgroundTasks",
        "OrchardCore.ContentFields.Indexing.SQL",
        "OrchardCore.Localization",
        "OrchardCore.ContentLocalization",
        "OrchardCore.ContentLocalization.ContentCulturePicker",
        "OrchardCore.Sitemaps",
        "OrchardCore.ContentLocalization.Sitemaps",
        "OrchardCore.DataProtection.Azure",
        "OrchardCore.Facebook",
        "OrchardCore.Facebook.Login",
        "OrchardCore.Facebook.Widgets",
        "OrchardCore.Forms",
        "OrchardCore.GitHub.Authentication",
        "OrchardCore.Google.Analytics",
        "OrchardCore.Google.GoogleAuthentication",
        "OrchardCore.HealthChecks",
        "OrchardCore.Https",
        "OrchardCore.Lucene.ContentPicker",
        "OrchardCore.Lucene.Worker",
        "OrchardCore.Media.Cache",
        "OrchardCore.Media.Azure.Storage",
        "OrchardCore.Microsoft.Authentication.MicrosoftAccount",
        "OrchardCore.MiniProfiler",
        "OrchardCore.OpenId",
        "OrchardCore.OpenId.Client",
        "OrchardCore.OpenId.Management",
        "OrchardCore.OpenId.Server",
        "OrchardCore.OpenId.Validation",
        "OrchardCore.PublishLater",
        "OrchardCore.Queries.Sql",
        "OrchardCore.ReCaptcha",
        "OrchardCore.ReCaptcha.Users",
        "OrchardCore.XmlRpc",
        "OrchardCore.RemotePublishing",
        "OrchardCore.ResponseCompression",
        "OrchardCore.ReverseProxy",
        "OrchardCore.Setup",
        "OrchardCore.Sitemaps.RazorPages",
        "OrchardCore.Taxonomies",
        "OrchardCore.Tenants",
        "OrchardCore.Tenants.FileProvider",
        "OrchardCore.Twitter",
        "OrchardCore.Twitter.Signin",
        "OrchardCore.Users.ChangeEmail",
        "OrchardCore.Users.ResetPassword",
        "OrchardCore.Users.TimeZone",
        "OrchardCore.Workflows",
        "OrchardCore.Workflows.Http",
        "OrchardCore.Workflows.Timers"
      ]
    }
  ]
}

STOP HERE: run the project again, you will see our two modules enabled.

LAST STEP: Delete App_Data folder and run the whole things, it will throws when creating the site.

deanmarcussen commented 4 years ago

Ok, thanks for posting here. Was too much to see what you were doing on gitter.

I think the approach is wrong. I would suggest adding the dependencies to your Manifest.cs

[assembly: Module(
    Name = "MyWebsite Base Module",
    Author = "MyWebsite",
    Website = "",
    Version = "0.1",
    Description = "MyWebsite Base Module",
    Category = "MyWebsite"
    Dependencies = new string[] { which ever modules you are reliant on here. }
)]

This should correctly order your dependencies, and activate them from the main recipe, as this module is activated there.

juchom commented 4 years ago

Doing this suppose that I know what I need in advance, which is not the case.

I try to find a way to work with Orchard with other developers.

Starting a new project from the blank recipe, I thought I could create an empty module, create a startup recipe with it enabled by default.

Then each time I update my orchard configuration or content definitions, I would export it, version the migration in my module and deploy it at startup (a bit like entity framework).

Using modules may not by the right place to do it, but I can't figure out how to do this.

jtkech commented 4 years ago

@juchom

Yes, as suggested by @deanmarcussen you still need to update the manifest if your module has new dependencies relying on the services of another new module.

If, after updating the manifest dependencies, it still doesn't work, for infos i'm working on the following

There are some related issues in the sense that there are all using custom migrations recipes e.g. #7449, #7557. For some good reasons (e.g. database issues as yours), normally when executing a recipe, each recipe step is executed in its own scope, which is not the case of a migration recipe. Originaly, migrations and then migrations recipes were intended to only update the type definition table (as we are doing in the regular migation classes), but yes it is useful to automatically do other things through recipe steps when enabling a feature.

Your issue as seen on gitter

SqliteException: SQLite Error 1: 'no such table: AutoroutePartIndex'.

So, i will work on a more global solution related to the scopes of migraion recipes.

juchom commented 4 years ago

Thanks for your replies,

So when enabling a feature in Orchard, I export it as a recipe in my module and I also add it to the Dependencies in Manifest.cs, I can run from scratch the whole module & migrations.

Thanks a lot for your help!

jtkech commented 4 years ago

and I also add it to the Dependencies in Manifest.cs

Normally only the one your module relies on

Okay so i close this issue, feel free to re-open another issue if you have any other problem ;)

juchom commented 4 years ago

Hello,

The solution of using Dependencies works fine when enabling features.

When disabling a feature I can't use anymore the Dependencies and I face the same issue.