CMS & Content Platforms

Tích hợp Episerver Commerce 14 vào Optimizely CMS 12 (.NET 8)

By Ginbok9 min read

Giới thiệu

Việc thêm chức năng Episerver Commerce vào một hệ thống Optimizely CMS hiện có có vẻ khó khăn, đặc biệt khi xử lý cấu hình cơ sở dữ liệu, các loại nội dung catalog và quyền truy cập. Hướng dẫn này sẽ đưa bạn qua toàn bộ quy trình, bao gồm các lỗi thường gặp và cách khắc phục.

Bạn sẽ học được:

Điều kiện tiên quyết:


Bước 1: Cài đặt các gói Commerce

Đầu tiên, thêm các gói Commerce cần thiết vào solution của bạn. Nếu bạn đang sử dụng Central Package Management (CPM), hãy cập nhật tệp Directory.Packages.props:

<ItemGroup>
  <PackageVersion Include="EPiServer.Commerce" Version="14.42.1" />
  <PackageVersion Include="EPiServer.Commerce.Core" Version="14.42.1" />
  <PackageVersion Include="Microsoft.Data.SqlClient" Version="5.2.2" />
</ItemGroup>

Sau đó tham chiếu chúng trong các dự án của bạn:

CmsIv.Web/CmsIv.Web.csproj:

<ItemGroup>
  <PackageReference Include="EPiServer.Commerce" />
  <PackageReference Include="Microsoft.Data.SqlClient" />
</ItemGroup>

CmsIv.Model/CmsIv.Model.csproj:

<ItemGroup>
  <PackageReference Include="EPiServer.Commerce.Core" />
</ItemGroup>

Bước 2: Đăng ký Dịch vụ Commerce

Trong tệp Startup.cs, hãy thêm các dịch vụ Commerce vào service collection:

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddCmsAspNetIdentity<ApplicationUser>()
        .AddCms()
        .AddCommerce()  // Thêm dòng này
        .AddFind()
        .AddAdminUserRegistration()
        .AddEmbeddedLocalization<Startup>();
}

Bước 3: Cấu hình Kết nối Cơ sở Dữ liệu

Thêm chuỗi kết nối cơ sở dữ liệu Commerce vào tệp appsettings.Development.json của bạn:

{
  "ConnectionStrings": {
    "EPiServerDB": "Server=.;Database=cmsiv;User Id=sa;Password=YourPassword;TrustServerCertificate=True",
    "EcfSqlConnection": "Server=.;Database=cmsiv-commerce;User Id=sa;Password=YourPassword;TrustServerCertificate=True"
  }
}

Đối với môi trường Production (appsettings.Production.json):

{
  "ConnectionStrings": {
    "EPiServerDB": "Server=.;Database=cmsiv;User Id=sa;Password=YourPassword;TrustServerCertificate=True",
    "EcfSqlConnection": "Server=.;Database=cmsiv-commerce;User Id=sa;Password=YourPassword;TrustServerCertificate=True"
  }
}

Lưu ý: Sử dụng Xác thực Windows (Integrated Security=True) trong môi trường phát triển nếu SQL Server của bạn được cấu hình cho nó. Sử dụng Xác thực SQL cho môi trường sản xuất.


Bước 4: Tạo Cơ sở Dữ liệu Tự động

Tạo một module khởi tạo để tự động tạo cơ sở dữ liệu Commerce nếu nó chưa tồn tại:

CommerceDatabaseInitialization.cs:

using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.ServiceLocation;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace YourProject.Web.Initialization
{
    [InitializableModule]
    public class CommerceDatabaseInitialization : IInitializableModule
    {
        private ILogger<CommerceDatabaseInitialization> _logger;
        private IConfiguration _configuration;

        public void Initialize(InitializationEngine context)
        {
            _logger = context.Locate.Advanced.GetInstance<ILogger<CommerceDatabaseInitialization>>();
            _configuration = context.Locate.Advanced.GetInstance<IConfiguration>();

            try
            {
                var commerceConnectionString = _configuration.GetConnectionString("EcfSqlConnection");
                
                if (string.IsNullOrEmpty(commerceConnectionString))
                {
                    _logger.LogWarning("EcfSqlConnection not found. Skipping database initialization.");
                    return;
                }

                EnsureCommerceDatabaseExists(commerceConnectionString);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error during Commerce database initialization");
                throw;
            }
        }

        private void EnsureCommerceDatabaseExists(string connectionString)
        {
            var builder = new SqlConnectionStringBuilder(connectionString);
            var databaseName = builder.InitialCatalog;
            
            builder.InitialCatalog = "master";
            var masterConnectionString = builder.ConnectionString;

            using (var connection = new SqlConnection(masterConnectionString))
            {
                connection.Open();

                var checkDbCommand = connection.CreateCommand();
                checkDbCommand.CommandText = "SELECT database_id FROM sys.databases WHERE Name = @DatabaseName";
                checkDbCommand.Parameters.AddWithValue("@DatabaseName", databaseName);
                
                var databaseId = checkDbCommand.ExecuteScalar();

                if (databaseId == null)
                {
                    _logger.LogInformation($"Database '{databaseName}' does not exist. Creating...");

                    var createDbCommand = connection.CreateCommand();
                    createDbCommand.CommandText = $@"
                        CREATE DATABASE [{databaseName}]
                        COLLATE SQL_Latin1_General_CP1_CI_AS";
                    createDbCommand.ExecuteNonQuery();

                    _logger.LogInformation($"Database '{databaseName}' created successfully.");
                }
            }
        }

        public void Uninitialize(InitializationEngine context) { }
    }
}

Bước 5: Tạo các Loại Nội dung Catalog

Episerver Commerce yêu cầu các loại nội dung catalog được trang trí bằng thuộc tính [CatalogContentType]. Hãy tạo các loại cơ bản cho catalog của bạn:

GenericNode.cs (Danh mục):

using EPiServer.Commerce.Catalog.ContentTypes;
using EPiServer.Commerce.Catalog.DataAnnotations;
using EPiServer.DataAnnotations;
using System.ComponentModel.DataAnnotations;

namespace YourProject.Model.Commerce
{
    [CatalogContentType(
        DisplayName = "Generic Category",
        GUID = "D8D7F6E5-4C2D-7E1D-E0F9-A8B7C6D5E4F3",
        Description = "Generic category for products")]
    public class GenericNode : NodeContent
    {
        [CultureSpecific]
        [Display(Name = "Category Name", GroupName = SystemTabNames.Content)]
        public virtual string CategoryName { get; set; }

        [CultureSpecific]
        [Display(Name = "Description", GroupName = SystemTabNames.Content)]
        public virtual XhtmlString CategoryDescription { get; set; }
    }
}

GenericProduct.cs:

[CatalogContentType(
    DisplayName = "Generic Product",
    GUID = "A1B2C3D4-E5F6-7A8B-9C0D-1E2F3A4B5C6D",
    Description = "Generic product with variants")]
public class GenericProduct : ProductContent
{
    [Display(Name = "Product Name", GroupName = SystemTabNames.Content)]
    public virtual string ProductName { get; set; }

    [Display(Name = "Brand", GroupName = SystemTabNames.Content)]
    public virtual string Brand { get; set; }

    [Display(Name = "Description", GroupName = SystemTabNames.Content)]
    public virtual XhtmlString Description { get; set; }
}

GenericVariant.cs:

[CatalogContentType(
    DisplayName = "Generic Variant",
    GUID = "1A2B3C4D-5E6F-7A8B-9C0D-E1F2A3B4C5D6",
    Description = "Generic variant for products")]
public class GenericVariant : VariationContent
{
    [Display(Name = "Color", GroupName = SystemTabNames.Content)]
    public virtual string Color { get; set; }

    [Display(Name = "Size", GroupName = SystemTabNames.Content)]
    public virtual string Size { get; set; }
}

Quan trọng: GUID phải là các giá trị thập lục phân hợp lệ (0-9, A-F). Không sử dụng thuộc tính [ContentType] cùng với [CatalogContentType] vì điều này gây ra xung đột.


Bước 6: Đăng ký các Loại Nội dung Catalog

Tạo một module cấu hình để đăng ký các loại tùy chỉnh của bạn:

CommerceConfigurationModule.cs:

using EPiServer.Commerce.Catalog.ContentTypes;
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.ServiceLocation;
using Microsoft.Extensions.DependencyInjection;

namespace YourProject.Web.Initialization
{
    [InitializableModule]
    [ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
    [ModuleDependency(typeof(EPiServer.Commerce.Initialization.InitializationModule))]
    public class CommerceConfigurationModule : IConfigurableModule
    {
        public void ConfigureContainer(ServiceConfigurationContext context)
        {
            context.Services.Configure<CatalogOptions>(config =>
            {
                config.RegisterCatalogContent<GenericNode>();
                config.RegisterCatalogContent<GenericProduct>();
                config.RegisterCatalogContent<GenericVariant>();
            });
        }

        public void Initialize(InitializationEngine context) { }
        public void Uninitialize(InitializationEngine context) { }
    }
}

Bước 7: Gieo Dữ liệu Mẫu (Tùy chọn)

Tạo một trình gieo dữ liệu (data seeder) để tự động điền nội dung vào catalog của bạn:

CommerceDataSeeder.cs:

[InitializableModule]
[ModuleDependency(typeof(EPiServer.Commerce.Initialization.InitializationModule))]
public class CommerceDataSeeder : IInitializableModule
{
    private ILogger<CommerceDataSeeder> _logger;
    private IContentRepository _contentRepository;
    private ReferenceConverter _referenceConverter;
    private IContentSecurityRepository _contentSecurityRepository;

    public void Initialize(InitializationEngine context)
    {
        _logger = context.Locate.Advanced.GetInstance<ILogger<CommerceDataSeeder>>();
        _contentRepository = context.Locate.Advanced.GetInstance<IContentRepository>();
        _referenceConverter = context.Locate.Advanced.GetInstance<ReferenceConverter>();
        _contentSecurityRepository = context.Locate.Advanced.GetInstance<IContentSecurityRepository>();

        try
        {
            EnsureCatalogRootAccess();
            SeedCommerceData();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to seed Commerce data");
        }
    }

    private void EnsureCatalogRootAccess()
    {
        var catalogRoot = _referenceConverter.GetRootLink();
        var securityDescriptor = _contentSecurityRepository.Get(catalogRoot)
            .CreateWritableClone() as IContentSecurityDescriptor;
        
        if (securityDescriptor != null)
        {
            var accessControlEntry = new AccessControlEntry(
                EveryoneRole.RoleName,
                AccessLevel.Read | AccessLevel.Create | AccessLevel.Edit | 
                AccessLevel.Delete | AccessLevel.Publish | AccessLevel.Administer);
            
            securityDescriptor.AddEntry(accessControlEntry);
            _contentSecurityRepository.Save(catalogRoot, securityDescriptor, SecuritySaveType.Replace);
        }
    }

    private void SeedCommerceData()
    {
        var catalogs = _contentRepository.GetChildren<CatalogContent>(_referenceConverter.GetRootLink());
        if (catalogs.Any())
        {
            _logger.LogInformation("Catalogs already exist. Skipping seeding.");
            return;
        }

        // Create catalog, categories, products, and variants here
        // See full implementation in the repository
    }
}

Các Lỗi Thường Gặp và Giải pháp

Lỗi 1: "No process is on the other end of the pipe"

Nguyên nhân: Lỗi xác thực kết nối SQL Server.

Giải pháp:

Lỗi 2: "The 'SEO' tab has been defined more than once"

Nguyên nhân: Định nghĩa tab trùng lặp, xung đột với tab SEO tích hợp của Commerce.

Giải pháp: Xóa định nghĩa tab SEO tùy chỉnh khỏi tệp TabNames.cs của bạn.

Lỗi 3: "No Catalog Content models were found"

Nguyên nhân: Các loại nội dung Catalog không được đăng ký đúng cách.

Giải pháp:

Lỗi 4: "Access denied to content -1073741823__CatalogContent"

Nguyên nhân: Thư mục gốc catalog thiếu quyền truy cập thích hợp.

Giải pháp: Thiết lập bộ mô tả bảo mật (security descriptor) cho thư mục gốc catalog bằng cách sử dụng IContentSecurityRepository như đã chỉ ra trong Bước 7.

Lỗi 5: "Cannot open database 'cmsiv-commerce'"

Nguyên nhân: Cơ sở dữ liệu không tồn tại.

Giải pháp: Module CommerceDatabaseInitialization tự động tạo nó. Đảm bảo người dùng SQL của bạn có quyền CREATE DATABASE hoặc vai trò dbcreator.

Lỗi 6: "There are incomplete migration steps"

Nguyên nhân: Schema của Episerver Commerce chưa được khởi tạo.

Giải pháp: Chạy ứng dụng một lần để Episerver tự động khởi tạo schema cơ sở dữ liệu Commerce. Việc khởi tạo diễn ra sau khi cơ sở dữ liệu được tạo.


Xác minh

Sau khi hoàn thành tất cả các bước:

  1. Chạy ứng dụng của bạn
  2. Truy cập: https://localhost:5000/episerver/commerce/catalog
  3. Bạn sẽ thấy:
    • Catalog Mặc định (Default Catalog)
    • Các danh mục và sản phẩm mẫu (nếu bạn đã triển khai gieo dữ liệu)
    • Không có lỗi từ chối truy cập

Các Thực Hành Tốt Nhất

  1. Tạo Cơ sở Dữ liệu: Sử dụng các module khởi tạo tự động thay vì tạo cơ sở dữ liệu thủ công
  2. Quyền: Luôn đặt quyền truy cập thích hợp cho thư mục gốc catalog để giao diện Commerce hoạt động
  3. Loại Nội dung: Tạo các loại cơ sở chung (generic base types) có thể được mở rộng cho các nhu cầu sản phẩm cụ thể
  4. Gieo Dữ liệu: Triển khai gieo dữ liệu (seeding) cho môi trường phát triển để tăng tốc độ thử nghiệm
  5. Cấu hình: Sử dụng các chuỗi kết nối khác nhau cho mỗi môi trường (Development, Production)

Các Bước Tiếp Theo

Bây giờ Commerce đã được thiết lập, hãy xem xét:


Kết luận

Việc tích hợp Episerver Commerce vào một dự án Optimizely CMS hiện có bao gồm nhiều bước, nhưng làm theo hướng dẫn này một cách có hệ thống sẽ giúp bạn tránh được các lỗi thường gặp. Chìa khóa là đảm bảo cấu hình cơ sở dữ liệu, đăng ký loại nội dung và quyền truy cập đúng đắn.

Hãy nhớ kiểm tra kỹ lưỡng trong môi trường phát triển trước khi triển khai lên môi trường sản xuất và luôn cập nhật các gói của bạn để tận dụng các tính năng mới nhất và các bản vá bảo mật.

Tài nguyên:


Ghi chú của Tác giả: Hướng dẫn này dựa trên kinh nghiệm triển khai thực tế với Optimizely CMS 12 và Episerver Commerce 14. Tất cả các mẫu code đã được kiểm tra trong môi trường sản xuất.

Cập nhật lần cuối: Tháng 1 năm 2026
Phiên bản Optimizely: CMS 12.33.1, Commerce 14.42.1
Framework Mục tiêu: .NET 8.0

#Optimizely Ecommerce DotNet8
← Back to Articles
Tích hợp Episerver Commerce 14 vào Optimizely CMS 12 (.NET 8) - Ginbok