Skip to content

Update module

Introduction

This article describes how to update an existing CustomerReviews sample module from VC Platform version 2.x to 3.0.

Note

A sample module source code can be found here: https://github.com/VirtoCommerce/vc-samples/tree/release/3.0.0/CustomerReviews.

0. Before the update

  1. Only update from the latest VC v2.x versions is supported. Ensure that the latest v2 versions of the Platform and all modules are installed.
  2. Usually, you have more than one custom VC module. Create a dependency map between your and the VC modules before the update. That helps to update the modules smoothly.
  3. Ensure that all your unit tests are current and passing
  4. In case of any question or issue, submit a new topic to Virto Commerce Community
  5. Please read the The list of code breaking changes included in 3.0

1. Prerequisites

  • Visual Studio 2019 (v16.4 or later)

2. Make correct structure in solution and projects

  1. Open the folder of selected v2 module in file manager (e.g., Windows Explorer).
  2. If exists, delete packages folder.
  3. Delete Properties folder from each of the projects' folder.
  4. Convert the projects from ASP.NET to ASP.NET Core:

    1. Open *.csproj file of CustomerReviews.Web project in text editor (e.g., Notepad), clear whole file content and set it to this:
      <Project Sdk="Microsoft.NET.Sdk.Web">
          <PropertyGroup>
              <TargetFramework>netcoreapp3.1</TargetFramework>
              <OutputType>Library</OutputType>
          </PropertyGroup>
          <ItemGroup>
              <Compile Remove="dist\**" />
              <EmbeddedResource Remove="dist\**" />
              <None Remove="dist\**" />
          </ItemGroup>
      </Project>
      
    2. Replace all other *.csproj files' content with this:
      <Project Sdk="Microsoft.NET.Sdk">
          <PropertyGroup>
              <TargetFramework>netcoreapp3.1</TargetFramework>
          </PropertyGroup>
      </Project>
      
    3. Read this article for more info.
  5. Create src and tests subfolders in module's root folder (Windows Explorer)

  6. Move CustomerReviews.Core, CustomerReviews.Data, CustomerReviews.Web projects to src
  7. Move CustomerReviews.Test project to tests
  8. If exists, move module.ignore from CustomerReviews.Web project up to the same folder as CustomerReviews.sln is.
  9. Open CustomerReviews.sln solution in Visual Studio
  10. Remove all projects from the solution
  11. Add src and tests Solution Folders
  12. Add the existing CustomerReviews.Core, CustomerReviews.Data, CustomerReviews.Web projects to src folder
  13. Add the existing CustomerReviews.Test project to tests folder
  14. Remove all files related to .NET Framework 4.x in every project:
    • App.config
    • packages.config
    • Web.config, Web.Debug.config, Web.Release.config
    • if exists, delete CommonAssemblyInfo.cs
    • if exists, delete all *.nuspec files. (NuGet packages are now released without using this file.)
  15. Add references to projects:
    1. CustomerReviews.Data: add reference to CustomerReviews.Core project
    2. CustomerReviews.Web: add references to CustomerReviews.Core, CustomerReviews.Data projects
    3. CustomerReviews.Tests: add references to CustomerReviews.Core, CustomerReviews.Data, CustomerReviews.Web projects
  16. References to NuGet packages:
    1. CustomerReviews.Core: add reference to VirtoCommerce.Platform.Core package (latest version).
    2. CustomerReviews.Data:
      1. add reference to VirtoCommerce.Platform.Data package (latest version).
      2. add reference to Microsoft.EntityFrameworkCore.Tools package (same version as referenced in VirtoCommerce.Platform.Data).
  17. Add other NuGet dependency packages, if any exists in module.manifest.

3. Make changes in CustomerReviews.Core project

  1. If missing, add class ModuleConstants.cs for module constants:

    1. Inside ModuleConstants add sub-classes Security and Permissions
    2. Add sub-classes Settings and General containing settings' definitions of type SettingDescriptor. Move settings definitions from module.manifest to this class > Follow the structure as defined in ModuleConstants.cs in CustomerModule.
    3. Other constants.
  2. Update ICustomerReviewService.cs:

    • Refactor all methods to be asynchronous: return Task<>
    • Rename all methods to have suffix Async
  3. If there is a search service defined:

    1. Move CustomerReviewSearchCriteria class to Search sub-folder;
    2. Ensure, that CustomerReviewSearchCriteria inherits from SearchCriteriaBase;
    3. Create CustomerReviewSearchResult class in Search sub-folder;
    4. Ensure, that CustomerReviewSearchResult inherits from GenericSearchResult<CustomerReview>.
    5. Refactor ICustomerReviewSearchService to use CustomerReviewSearchResult, all methods be asynchronous, and end with Async:
      public interface ICustomerReviewSearchService
      {
          Task<CustomerReviewSearchResult> SearchCustomerReviewsAsync(CustomerReviewSearchCriteria criteria);
      }
      
  4. If any model-related changing/changed events were defined in Events folder, ensure that each of them derive from the base GenericChangedEntryEvent class.

  5. If any custom Notifications were added to Notifications folder:

    1. Add reference to NuGet VirtoCommerce.NotificationsModule.Core package;
    2. Ensure that each defined Notification class inherits from EmailNotification/SmsNotification or own class, based on Notification.

4. Make changes in CustomerReviews.Data project

  1. Repositories folder

    1. Create CustomerReviewsDbContext.cs

      • Add new class CustomerReviewsDbContext
      • Make it public and derive from DbContextWithTriggers
      • Add 2 constructors, using CustomerDbContext as an example.

        Note: 1 constructor is public and another is protected.

      • Override OnModelCreating method, add CustomerReviewEntity mapping to modelBuilder and set max length to Id:
        modelBuilder.Entity<CustomerReviewEntity>().ToTable("CustomerReview").HasKey(x => x.Id);
        modelBuilder.Entity<CustomerReviewEntity>().Property(x => x.Id).HasMaxLength(128).ValueGeneratedOnAdd();
        

        Note: a factory for creating derived Microsoft.EntityFrameworkCore.DbContext instances.

    2. Create DesignTimeDbContextFactory.cs

      • Add new class DesignTimeDbContextFactory
      • Make it public and derive from IDesignTimeDbContextFactory<CustomerReviewsDbContext>
      • Implement CreateDbContext method, using CustomerModule.Data/Repositories/DesignTimeDbContextFactory.cs as an example
      • Ensure that connection string to your development SQL Server in UseSqlServer method is correct. It would be used while generating code-first migrations.
    3. Update ICustomerReviewsRepository.cs
      • If the module is an extension then derive from derived module's interface repository.
      • Refactor all methods to be asynchronous: return Task<>
      • Rename all methods to have suffix Async
    4. Update CustomerReviewsRepository.cs
      • Refactor CustomerReviewsRepository class to derive from DbContextRepositoryBase<CustomerReviewsDbContext> or if the module is an extension then derive from derived module's repository.
      • Refactor the constructors to leave only one, taking the only CustomerReviewsDbContext parameter:
        public CustomerReviewRepository(CustomerReviewsDbContext dbContext) : base(dbContext)
        {
        }
        
      • Refactor CustomerReviews property to access data using DbSet like this:
        public IQueryable<CustomerReviewEntity> CustomerReviews => DbContext.Set<CustomerReviewEntity>();
        
      • Remove OnModelCreating method
  2. Caching folder

    1. If missing, create Caching folder. This folder is for the cache region classes. Typically, each model should have its own region.
    2. Derive CacheRegion from generic CancellableCacheRegion<T> class:
      public class CustomerReviewCacheRegion : CancellableCacheRegion<CustomerReviewCacheRegion>
      {
      }
      
  3. Services folder

    1. All services: remove inheritance from ServiceBase
    2. Ensure that the signatures of the methods matches the ones defined in the corresponding interfaces
    3. Change response to Task<CustomerReviewSearchResult> in CustomerReviewSearchService service
    4. Refactor all methods to be asynchronous
    5. Add working with cache to all methods

      check this example for more details VirtoCommerce.CustomerModule.Data.Services.

  4. Migrations folder

    1. Create InitialCustomerReviews migration

      1. Open Configuration.cs and copy the namespace to your notes. (Will need it in the next section.)
      2. Delete everything (all migrations and Configuration.cs) from Migrations folder
      3. Execute "Unload Project" on CustomerReviews.Web project in Solution Explorer (or the solution would fail to build in this step due to the errors in this project).
      4. Execute "Set as Startup Project" on CustomerReviews.Data project in Solution Explorer
      5. Open NuGet Package Manager Console
      6. Select "src\CustomerReviews.Data" as "Default project"
      7. Run command:
        Add-Migration InitialCustomerReviews -Verbose
        
      8. In case of any existing module's extension is developed, study and follow the steps from How to extend the DB model of VC module guide.
    2. Create Migration for backward compatibility with v2.x

      1. Add new migration with name UpdateCustomerReviewsV2 and rename the migration filename to 20000000000000_UpdateCustomerReviewsV2. Mind the name format: "20000000000000_Update{ModuleName}V2".
      2. Add SQL command to the migration:
        migrationBuilder.Sql(@"IF (EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '__MigrationHistory'))
                IF (EXISTS (SELECT * FROM __MigrationHistory WHERE ContextKey = 'CustomerReviews.Data.Migrations.Configuration'))
                    BEGIN
                        INSERT INTO [dbo].[__EFMigrationsHistory] ([MigrationId],[ProductVersion]) VALUES ('20191129134041_InitialCustomerReviews', '2.2.3-servicing-35854')
                    END");
        
      3. the ContextKey value is the V2 migration Configuration name, including namespace. Retrieve the namespace from your notes, as you put it there in the previous section. Typically, the value is "{ModuleId}.Data.Migrations.Configuration".
      4. the value for MigrationId has to be the name of your new migration, added in previous step. ('20191129134041_InitialCustomerReviews' in our case). Check 20000000000000_UpdateCoreV2.cs migration as another example.
      5. value for ProductVersion should be taken from 20000000000000_UpdateCustomerReviewsV2.Designer line 19:
        .HasAnnotation("ProductVersion", "2.2.3-servicing-35854")
        
      6. Open 20000000000000_UpdateCustomerReviewsV2.Designer and change Migration attribute parameter value to the current migration ID ("20000000000000_UpdateCustomerReviewsV2" in this case). Check 20000000000000_UpdateCoreV2.Designer.cs as another example.

        Note: if there are extended entities then need to explicitly update Discriminator to extended type. For more details please check extension article Discriminator column creation part.

  5. If separated databases are used by the solution, follow the steps in Prepare separated databases for VC v3.

  6. It's useful sometimes to apply migrations without starting the platform. There is an utility to extract and apply migrations (integrated to vc-build tool). Take a reference to Grab migrator utility.

5. Make changes in CustomerReviews.Web project

  1. Execute "Reload Project" on CustomerReviews.Web project in Solution Explorer (as it was unloaded earlier).
  2. Changes in module.manifest
    1. Versioning - increase major module version and add prerelease tag (empty value for a release version):
      <version>3.0.0</version>
      <version-tag></version-tag>
      
    2. Required minimal version of VC Platform:
      <platformVersion>3.0.0</platformVersion>
      
    3. Module dependencies - change to actual versions:
      <dependencies>
          <dependency id="VirtoCommerce.Core" version="3.1.0" />
      </dependencies>
      
    4. Remove styles, scripts sections from the manifest file.
  3. Add localizations for permissions/settings to Localizations/en.CustomerReviews.json file as this. Sample resulting keys: "permissions.customerReview:read", "settings.CustomerReviews.CustomerReviewsEnabled.title".
  4. Remove permissions, settings definitions sections from the manifest file.

  5. Changes in Module.cs

    1. Change the class inheritance to interface IModule, then add implementation methods: Initialize, PostInitialize, Uninstall and property ModuleInfo. Check VirtoCommerce.CustomerModule.Web/Module.cs for another implementation example.
    2. Read the Dependency injection in ASP.NET Core article for additional info.
    3. Register all the needed classes for dependency injection inside Initialize method, like CustomerReviewDbContext, CustomerReviewRepository, etc.
    4. Register settings using interface ISettingsRegistrar in PostInitialize method:
      var settingsRegistrar = appBuilder.ApplicationServices.GetRequiredService<ISettingsRegistrar>();
      settingsRegistrar.RegisterSettings(ModuleConstants.Settings.AllSettings, ModuleInfo.Id);
      
    5. Register permissions using interface IPermissionsRegistrar in PostInitialize method:
      var permissionsProvider = appBuilder.ApplicationServices.GetRequiredService<IPermissionsRegistrar>();
      permissionsProvider.RegisterPermissions(ModuleConstants.Security.Permissions.AllPermissions.Select(x => new Permission() { GroupName = "CustomerReview", Name = x }).ToArray());
      
    6. Add this code into PostInitialize method, needed to ensure that the migrations would be applied:
      using (var serviceScope = appBuilder.ApplicationServices.CreateScope())
      {
          var dbContext = serviceScope.ServiceProvider.GetRequiredService<CustomerReviewsDbContext>();
          dbContext.Database.MigrateIfNotApplied(MigrationName.GetUpdateV2MigrationNameByOwnerName(ModuleInfo.Id, <<your company prefix in moduleId>>));
          dbContext.Database.EnsureCreated();
          dbContext.Database.Migrate();
      }
      

    Note

    The MigrateIfNotApplied extension method is needed for the database backward compatibility with version 2.x. This extension enables to skip generating the initial migration, as there are changes (tables, indexes) in the database already.

  6. Changes to all API Controllers in Controllers/Api folder:

    1. Refactor controllers to derive from Microsoft.AspNetCore.Mvc.Controller.
    2. Change RoutePrefix attribute to Route for all endpoints
    3. Remove ResponseType attribute from all endpoints
    4. Change CheckPermission attribute to Authorize for all endpoints

      If the endpoint should have a restricted access, an Authorize attribute with the required permission should be added. Use the ModuleConstants class, which was previously defined in CustomerReviews.Core project.

    5. Review the exposed endpoints and refactor to be asynchronous (return async Task<>), if needed

    6. Mark each complex type parameter with [FromBody] attribute for all endpoints. The attribute for Delete endpoint should be [FromQuery]. E.g., SearchCustomerReviews method converted to ASP.NET Core MVC:
      [HttpPost]
      [Route("search")]
      [Authorize(ModuleConstants.Security.Permissions.Read)]
      public async Task<ActionResult<CustomerReviewSearchResult>> SearchCustomerReviews([FromBody]CustomerReviewSearchCriteria criteria)
      
      Read Controller action return types in ASP.NET Core web API for more info.
  7. If there are any JavaScript or stylesheet files in the project:

    1. Copy package.json from sample package.json;
    2. Copy webpack.config.js from sample webpack.config.js;
    3. Change the namespace on line 36 in webpack.config.js to be equal to module's identifier:
      namespace: 'CustomerReviews'
      
    4. Open Command prompt and navigate to CustomerReviews.Web folder. Run:

      npm install
      

      Note

      fix any css errors, if the previous command would fail. For CustomerReviews sample also change line 12 in webpack.config.js to:

      ...glob.sync('./Content/css/*.css', { nosort: true })
      

    5. Add dist/ line to .gitignore file;

    6. Add node_modules/ line to .gitignore file;
    7. Install WebPack Task Runner extension to Visual Studio (restart required);
    8. Build the scripts:

      1. Locate and right-click on webpack.config.js in Solution Explorer
      2. Execute "Task Runner Explorer"
      3. Double-click "Run - Development" in "Task Runner Explorer"

      Note

      The resulting file(s) (app.js, style.css) were generated to *.Web/dist folder.

6. Make changes in CustomerReviews.Tests project

  1. Reference the required NuGet packages by adding this ItemGroup to project file:
        <ItemGroup>
            <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
            <PackageReference Include="Moq" Version="4.13.1" />
            <PackageReference Include="MSTest.TestAdapter" Version="2.0.0" />
            <PackageReference Include="MSTest.TestFramework" Version="2.0.0" />
            <PackageReference Include="xunit" Version="2.4.1" />
            <PackageReference Include="xunit.runner.console" Version="2.4.1">
                <PrivateAssets>all</PrivateAssets>
                <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
            </PackageReference>
            <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
                <PrivateAssets>all</PrivateAssets>
                <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
            </PackageReference>
            <DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
        </ItemGroup>
    
  2. Add TestAsyncQueryProvider class under Common folder. Paste the class implementation from here.
  3. Remove inheritance from FunctionalTestBase for each tests class.
  4. Add the integration tests under IntegrationTests folder. Ensure, that each integration tests class is marked with this Trait attribute:
    [Trait("Category", "IntegrationTest")]
    

7. Create module package

  1. Please, read the article about VirtoCommerce.GlobalTool.
  2. Add .nuke file to be able to use VirtoCommerce.GlobalTool. It should contain the solution filename, e.g., .nuke in vc-module-customer
  3. Add Directory.Build.props file to be able to configure package release versions. Check Directory.Build.props in vc-module-customer for details.

Last update: March 19, 2021