Sitecore Experience Commerceのシステム エンティティのスキーマのカタログを拡張する方法


解説

Sitecore Experience Commerceでは、Sellable Items (Product)、CategoriesおよびCatalogのデフォルトのスキーマが提供されています。この記事は、プログラムでデフォルトのスキーマを拡張する方法について説明します。

Sitecore Experience Commerce 9.0.2から、スキーマを拡張するためのビジネス ツールのUIのメカニズムを利用することができます。詳細情報については、コマース アイテムにプロパティを追加する方法およびエンティティ コンポーザーの記事をご参照ください。

解決策

本節では、コンポーネントでSellable Itemを拡張する方法について説明します。他のカタログのシステム エンティティも同様の方法で拡張することができます。コマースのプラグインに関する詳細情報については、「最初のプラグインの作成」記事をご参照ください。本記事で参照しているコード スニペットは、Plugin.Sample.Notes.zip サンプル プロジェクトに含まれています(Sitecore Experience Commerce 10.xと互換性のある、更新されたPlugin.Sample.Notes.XC10.x.zipサンプル プロジェクトを使用することができます。この新しいバージョンでは、ローカライズのUIおよびビジネス ツールでのエンティティ バージョンの適切な取り扱いをよりよく対応できる変更も含まれています)。

Sellable Itemのエンティティを拡張するには、Sellable Itemの一部として永続化されるプロパティを持つ新しいコンポーネント クラスを作成します:

namespace Plugin.Sample.Notes
{
  using Sitecore.Commerce.Core;
 
  public class NotesComponents : Component
  {
    public string WarrantyInformation { get; set; } = string.Empty;
    public string InternalNotes { get; set; } = string.Empty;
  }
}

ユーザがコンテンツをこれらのプロパティに提供できるよう、新しいエンティティのビューを作成し、IGetEntityViewPipelineに登録することで、sellableアイテムおよびそのバリアントの既存の表示を拡張することができます。

エンティティのビューを作成する場合、1つのエンティティのビューが要求された際、パイプラインのすべてのブロックが実行される可能性があります。従って、ブロックが特定のビュー名、アクション名またはエンティティのタイプにのみ動作することを確認する必要があります。

次のコード サンプルは、Sellable Itemのビューを処理し、上記で作成したコンポーネントのプロパティを、ビジネス ツールでレンダリングされたビュー プロパティとして追加します。

namespace Plugin.Sample.Notes
{
  using System;
  using System.Threading.Tasks;
  using Sitecore.Commerce.Core;
  using Sitecore.Commerce.EntityViews;
  using Sitecore.Commerce.Plugin.Catalog;
  using Sitecore.Framework.Conditions;
  using Sitecore.Framework.Pipelines;
 
  [PipelineDisplayName(NotesConstants.Pipelines.Blocks.GetNotesViewBlock)]
  public class GetNotesViewBlock : PipelineBlock<EntityView, EntityView, CommercePipelineExecutionContext>
  {
    private readonly ViewCommander _viewCommander;
 
    public GetNotesViewBlock(ViewCommander viewCommander)
    {
      this._viewCommander = viewCommander;
    }
 
    public override Task<EntityView> Run(EntityView arg, CommercePipelineExecutionContext context)
    {
      Condition.Requires(arg).IsNotNull($"{Name}: The argument cannot be null.");
      var request = this._viewCommander.CurrentEntityViewArgument(context.CommerceContext);
      var catalogViewsPolicy = context.GetPolicy<KnownCatalogViewsPolicy>();
      var notesViewsPolicy = context.GetPolicy<KnownNotesViewsPolicy>();
      var notesActionsPolicy = context.GetPolicy<KnownNotesActionsPolicy>();
      var isVariationView = request.ViewName.Equals(catalogViewsPolicy.Variant, StringComparison.OrdinalIgnoreCase);
      var isConnectView = arg.Name.Equals(catalogViewsPolicy.ConnectSellableItem, StringComparison.OrdinalIgnoreCase);
 
      // Make sure that we target the correct views
      if (!isCatalogView && !isConnectView)
      {
        return Task.FromResult(arg);
      }
 
      // Only proceed if the current entity is a sellable item
      if (!(request.Entity is SellableItem))
      {
        return Task.FromResult(arg);
      }
 
      var sellableItem = (SellableItem)request.Entity;
 
      // See if we are dealing with the base sellable item or one of its variations.
      var variationId = string.Empty;
      if (isVariationView && !string.IsNullOrEmpty(arg.ItemId))
      {
        variationId = arg.ItemId;
      }
 
      var targetView = arg;
 
      // Check if the edit action was requested
      var isEditView =
      !string.IsNullOrEmpty(arg.Action) &&
      arg.Action.Equals(
        notesActionsPolicy.EditNotes,
        StringComparison.OrdinalIgnoreCase);
 
      if (!isEditView)
      {
        // Create a new view and add it to the current entity view.
        var view = new EntityView
        {
          Name = context.GetPolicy<KnownNotesViewsPolicy>().Notes,
          DisplayName = "Notes",
          EntityId = arg.EntityId,
          ItemId = variationId
        };
 
        arg.ChildViews.Add(view);
 
        targetView = view;
        }
 
      if (sellableItem != null && (sellableItem.HasComponent<NotesComponents>(variationId) || isConnectView || isEditView))
        {
          var component = sellableItem.GetComponent<NotesComponents>(variationId);
 
          targetView.Properties.Add(
          new ViewProperty
          {
            Name = nameof(NotesComponents.WarrantyInformation),
            RawValue = component.WarrantyInformation,
            IsReadOnly = !isEditView,
            IsRequired = false
          });
 
          // Add additional properties
      }
      return Task.FromResult(arg);
    }
  }
}


ストアフロントでコンテンツにアクセスできるようにするには、上に示したようにConnectSellableItemビューを処理する必要があります。これにより、テンプレートの生成で新しいビューをピックアップすることができるようになります。

次に、ユーザーの入力を受け取り、与えられたSellable Itemのコンポーネントにそのユーザーの入力を永続化させるアクションを作成する必要があります。

namespace Plugin.Sample.Notes
{
  using System;
  using System.Linq;
  using System.Threading.Tasks;
  using Sitecore.Commerce.Core;
  using Sitecore.Commerce.EntityViews;
  using Sitecore.Commerce.Plugin.Catalog;
  using Sitecore.Framework.Conditions;
  using Sitecore.Framework.Pipelines;
 
  [PipelineDisplayName(NotesConstants.Pipelines.Blocks.DoActionEditNotesBlock)]
  public class DoActionEditNotesBlock : PipelineBlock<EntityView, EntityView, CommercePipelineExecutionContext>
  {
    private readonly CommerceCommander _commerceCommander;
 
    public DoActionEditNotesBlock(CommerceCommander commerceCommander)
    {
      this._commerceCommander = commerceCommander;
    }
 
    public override Task<EntityView> Run(EntityView arg, CommercePipelineExecutionContext context)
    {
      Condition.Requires(arg).IsNotNull($"{Name}: The argument cannot be null.");
      var notesActionsPolicy = context.GetPolicy<KnownNotesActionsPolicy>();

      // Only proceed if the right action was invoked
      if (string.IsNullOrEmpty(arg.Action) || !arg.Action.Equals(notesActionsPolicy.EditNotes, StringComparison.OrdinalIgnoreCase))
      {
        return Task.FromResult(arg);
      }
 
      // Get the sellable item from the context
      var entity = context.CommerceContext.GetObject<SellableItem>(x => x.Id.Equals(arg.EntityId));
      if (entity == null)
      {
        return Task.FromResult(arg);
      }
 
      // Get the notes component from the sellable item or its variation
      var component = entity.GetComponent<NotesComponents>(arg.ItemId);
 
      // Map entity view properties to component
      component.WarrantyInformation = arg.Properties.FirstOrDefault(x => x.Name.Equals(nameof(NotesComponents.WarrantyInformation), StringComparison.OrdinalIgnoreCase))?.Value;
      component.InternalNotes = arg.Properties.FirstOrDefault(x => x.Name.Equals(nameof(NotesComponents.InternalNotes), StringComparison.OrdinalIgnoreCase))?.Value;
 
      // Persist changes
      this._commerceCommander.Pipeline<IPersistEntityPipeline>().Run(new PersistEntityArgument(entity), context);
 
      return Task.FromResult(arg);
    }
  }
}

ここで扱うデータに応じて、何らかの変更を永続化する前に、ユーザー入力を検証することを強くお勧めします。

これで、ビジネス ツールのコンテンツをレンダリングし、エンティティへの変更を永続化できる2つのブロックを持っています。

ユーザーがビジネス ツール内からビュー コンテンツを編集できるようにするには、ビュー アクションを作成する必要があります:

namespace Plugin.Sample.Notes
{
  using System;
  using System.Threading.Tasks;
  using Sitecore.Commerce.Core;
  using Sitecore.Commerce.EntityViews;
  using Sitecore.Framework.Conditions;
  using Sitecore.Framework.Pipelines;
 
  [PipelineDisplayName(NotesConstants.Pipelines.Blocks.PopulateNotesActionsBlock)]
  public class PopulateNotesActionsBlock : PipelineBlock<EntityView, EntityView, CommercePipelineExecutionContext>
  {
    public override Task<EntityView> Run(EntityView arg, CommercePipelineExecutionContext context)
    {
      Condition.Requires(arg).IsNotNull($"{Name}: The argument cannot be null.");
      var viewsPolicy = context.GetPolicy<KnownNotesViewsPolicy>();
 
      if (string.IsNullOrEmpty(arg?.Name) || !arg.Name.Equals(viewsPolicy.Notes, StringComparison.OrdinalIgnoreCase))
      {
        return Task.FromResult(arg);
      }
 
      var actionPolicy = arg.GetPolicy<ActionsPolicy>();
 
      actionPolicy.Actions.Add(
        new EntityActionView
        {
          Name = context.GetPolicy<KnownNotesActionsPolicy>().EditNotes,
          DisplayName = "Edit Sellable Item Notes",
          Description = "Edits the sellable item notes",
          IsEnabled = true,
          EntityView = arg.Name,
          Icon = "edit"
        });
 
      return Task.FromResult(arg);
    }
  }
}

最後に、すべての異なるブロックがそれぞれのパイプラインにプログラミングされます。パイプラインの設定は、プラグインのConfigureSitecoreクラスで実施されます。

namespace Plugin.Sample.Notes
{
  using System.Reflection;
  using Microsoft.Extensions.DependencyInjection;
  using Sitecore.Commerce.Core;
  using Sitecore.Commerce.EntityViews;
  using Sitecore.Commerce.Plugin.Catalog;
  using Sitecore.Framework.Configuration;
  using Sitecore.Framework.Pipelines.Definitions.Extensions;
 
  public class ConfigureSitecore : IConfigureSitecore
  {
    public void ConfigureServices(IServiceCollection services)
    {
      var assembly = Assembly.GetExecutingAssembly();
      services.RegisterAllPipelineBlocks(assembly);
 
        services.Sitecore().Pipelines(config => config
          .ConfigurePipeline<IGetEntityViewPipeline>(c =>
          {
            c.Add<GetNotesViewBlock>().After<GetSellableItemDetailsViewBlock>();
          })
          .ConfigurePipeline<IPopulateEntityViewActionsPipeline>(c =>
          {
            c.Add<PopulateNotesActionsBlock>().After<InitializeEntityViewActionsBlock>();
          })
          .ConfigurePipeline<IDoActionPipeline>(c =>
          {
            c.Add<DoActionEditNotesBlock>().After<ValidateEntityVersionBlock>();
          })
      );
    }
  }
}

上記のようにConnect固有のビューを処理し、新しく作成されたプロパティがストアフロントで利用できるようにする場合、Sitecore XPのインスタンスのコンテンツ エディタを開き、以下の手順を実施してください: 

  1. Sitecore Experience Commerceのキャッシュを更新します:
    1. コマースのタブをクリックします。
    2. リボンにて「Refresh Commerce Cache(コマースのキャッシュを更新する)」をクリックします。
  2. データ テンプレートを更新します。
    1. 「コマース」のタブにて、「Delete Data Templates(データ テンプレートを削除する)」をクリックします。
    2. 次に、「Update Data Templates(データ テンプレートを更新する)」をクリックします。
  3. データ テンプレートを更新した後、パブリッシュを実施します。