How to Extend Customer View in Sitecore Experience Commerce 9 In a Plugin
There may be an occasion where one may need to extend a Customer view from within a plugin.
One way is to use Composer, another way is to use entity view. We will be using entity view here.
Assuming we need to create a tax plugin where we may need to set tax excemption code for customers like charities or millitary who do not need to tax on their purchases
If the code is set, during checkout, the customer will not be taxed on the cart items.
First we start by adding required NuGet packages to the plugin if they are not already added.
First, we add Sitecore.Commerce.Plugin.Customer
It is important to add the right version.
The right version for Sitecore Experience Commerce 9.2 is 4.0.16, for 9.1 is 3.0.11, for version 9.0.3 is 2.4.5
Next we add Sitecore.Commerce.Plugin.Views
The right version for Sitecore Experience Commerce 9.2 is 4.0.72, for 9.1 is 3.0.17, for version 9.0.3 is 2.4.37
In the plugin, we will create a folder and name it Customer under the /Pipelines/Blocks folder
Before I proceed, I will like to say that it is entirely up to you how you structure your plugin based on your internal coding standards.
I am just following Sitecore's plugin stucture to an extent.
Under the new Customer folder, we will create 3 public classes
The first class will be named "GetCustomerTaxExemptCodeViewBlock"
The second class will be named "EditCustomerTaxExemptCodeBlock"
The third class will be named "PopulateCustomerTaxExemptCodeActionBlock"
Starting with the GetCustomerTaxExemptCodeViewBlock class.
The purpose as the name suggests it is to return the view that will be populated with the data of interest which in this case is the Customer Tax ExemptCode.
It must inherit from "PipelineBlock
It could also benefit from having Entity view "ViewCommander" injected through its controller.
The code below shows the completed class with comments for guidance.
The string "CustomerTaxExcemptionCodeView" should be unique for the view we intend to display or use for the feature we are developing.
public class GetCustomerTaxExemptCodeViewBlock : PipelineBlock
{
public GetCustomerTaxExemptCodeViewBlock(ViewCommander viewCommander)
{
_viewCommander = viewCommander;
}
private readonly ViewCommander _viewCommander;
public override Task Run(EntityView arg, CommercePipelineExecutionContext context)
{
// get the incomming request from the pipeline, so that one can filter for the relevant requests
var request = _viewCommander.CurrentEntityViewArgument(context.CommerceContext);
// Only proceed if the current entity is a customer item else return to the pipeline flow
if (!(request.Entity is Customer)) return Task.FromResult(arg);
// Get the customer object incase the properties are needed later
var customer = (Customer)request.Entity;
// Get the Sitecore Commerce predefined customer view policy so that its properties can be used to filter the request coming from the pipeline
var customerViewsPolicy = context.GetPolicy();
// From the predefined customer view policy get settings for or detail view or master view or edit view which relates to the "CustomerTaxExcemptionCodeView" we need to target
var isDetailtView = arg.Name.Equals(customerViewsPolicy.Details, StringComparison.OrdinalIgnoreCase);
var isMasterView = arg.Name.Equals(customerViewsPolicy.Master, StringComparison.OrdinalIgnoreCase);
var isEditView = !string.IsNullOrEmpty(arg.Action) && arg.Action.Equals("CustomerTaxExcemptionCodeView", StringComparison.OrdinalIgnoreCase);
// Make sure that we target the correct views
// If the view in the request is not master or detali or "CustomerTaxExcemptionCodeView", we return control back to the pipeline
if (string.IsNullOrEmpty(request.ViewName) ||
!request.ViewName.Equals(customerViewsPolicy.Master, StringComparison.OrdinalIgnoreCase) &&
!request.ViewName.Equals(customerViewsPolicy.Details, StringComparison.OrdinalIgnoreCase) &&
!request.ViewName.Equals("CustomerTaxExcemptionCodeView", StringComparison.OrdinalIgnoreCase)
)
{
return Task.FromResult(arg);
}
var targetView = arg;
// Check if the edit action was requested, otherwise add
if (!isEditView)
{
// Create a new view and add it to the current entity view.
var view = new EntityView
{
Name = "CustomerTaxExcemptionCodeView",
DisplayName = "Customer Tax Excemption Code Setting",
EntityId = arg.EntityId,
ItemId = arg.ItemId,
EntityVersion = customer.EntityVersion,
};
arg.ChildViews.Add(view);
targetView = view;
}
if (isMasterView || isDetailtView || isEditView)
{
var customerTaxExcemptionCode = string.Empty;
//// You may get the context customer's customerTaxExcemptionCode from the data store where you stored it either could be database, api end point, as a component on the context customer object,
//customerTaxExcemptionCode = GetCustomerExceptionCode(customer.Id);
targetView.Properties.Add(
new ViewProperty
{
Name = "TaxExcemptionCode",
DisplayName = "Tax Exemption Code",
RawValue = customerTaxExcemptionCode,
Value = customerTaxExcemptionCode,
IsReadOnly = !isEditView,
IsRequired = false
});
}
return Task.FromResult(arg);
}
}
When done, we need to open the "ConfigureSitecore.cs" class under the plugin and add code to chain GetCustomerTaxExemptCodeViewBlock to the "IGetEntityViewPipeline" pipeline so that it will be called when views for the customer are getting loaded.
We will configure it by adding the code below.
.ConfigurePipeline(c =>
{
c.Add().After();
})
After adding it, it becomes
public void ConfigureServices(IServiceCollection services)
{
var assembly = Assembly.GetExecutingAssembly();
services.RegisterAllPipelineBlocks(assembly);
services.Sitecore().Pipelines(config => config
.ConfigurePipeline(c =>
{
c.Add().After();
})
.ConfigurePipeline(configure => configure.Add()));
services.RegisterAllCommands(assembly);
}
Now lets test out the feature.
From Sitecore.Commerce.Engine project, add your plugin project as a reference.
Deploy the code and go to business tools, open any customer profile and you should see the new "Customer Tax Excemption Code Setting" as shown in the figure below.
Fig1
Now we need a way to set the value of the Excemption code for a user. Lets do that.
===
We will now go to the "PopulateCustomerTaxExemptCodeActionBlock" class we created earlier.
Make it inherit from "PipelineBlock
The code below shows the completed class with comments for guidance.
public class PopulateCustomerTaxExemptCodeActionBlock : PipelineBlock
{
// Abstract member implementation.
public override Task Run(EntityView arg, CommercePipelineExecutionContext context)
{
// Ensure that it only proceeds when the view name is "CustomerTaxExcemptionCodeView" otherwise, return control to the pipeline
if (string.IsNullOrEmpty(arg?.Name) || !arg.Name.Equals("CustomerTaxExcemptionCodeView", StringComparison.OrdinalIgnoreCase))
{
return Task.FromResult(arg);
}
// Get the action policies on the view
var actionPolicy = arg.GetPolicy();
// Update the view's action policy by adding a new action that can be performed on the view
actionPolicy.Actions.Add(
new EntityActionView
{
Name = "CustomerTaxExcemptionCodeView",
DisplayName = "Edit Excemption Code",
Description = "Edit Customer Tax Excemption Code",
IsEnabled = true,
EntityView = arg.Name,
Icon = "edit"
});
// Return control to the pipeline
return Task.FromResult(arg);
}
}
When done, we need to open the "ConfigureSitecore.cs" class under the plugin and add code to chain PopulateCustomerTaxExemptCodeActionBlock to the "InitializeEntityViewActionsBlock" pipeline so that it will be called when views for the customer are getting loaded.
We will configure it by adding the code below.
.ConfigurePipeline(c =>
{
c.Add().After();
})
After adding it, it becomes
public void ConfigureServices(IServiceCollection services)
{
var assembly = Assembly.GetExecutingAssembly();
services.RegisterAllPipelineBlocks(assembly);
services.Sitecore().Pipelines(config => config
.ConfigurePipeline(c =>
{
c.Add().After();
})
.ConfigurePipeline(c =>
{
c.Add().After();
})
.ConfigurePipeline(configure => configure.Add()));
services.RegisterAllCommands(assembly);
}
Now lets test out the feature.
Deploy the code and go to business tools,
open any customer profile and you should see that the new "Customer Tax Excemption Code Setting" view has a means of editing its value as shown in the figure below.
Fig2
When we click the edit button, a form pops up for us to add the excemption code as shown in the figure below.
Fig 3
Now, we need to be able to edit the value and save it.
Now, lets do that.
We will now go to the "EditCustomerTaxExemptCodeBlock" class we created earlier.
Make it inherit from "PipelineBlock
It could also benefit from having Entity view "CommerceCommander" injected through its controller.
The code below shows the completed class with comments for guidance.
public class EditCustomerTaxExemptCodeBlock : PipelineBlock
{
// Inject CommerceCommander into the constructor
public EditCustomerTaxExemptCodeBlock(CommerceCommander commerceCommander)
{
_commerceCommander = commerceCommander;
}
private readonly CommerceCommander _commerceCommander;
public override Task Run(EntityView arg, CommercePipelineExecutionContext context)
{
// Only proceed if the right action was invoked
if (string.IsNullOrEmpty(arg.Action) || !arg.Action.Equals("CustomerTaxExcemptionCodeView", StringComparison.OrdinalIgnoreCase))
{
return Task.FromResult(arg);
}
// Get the customer entity from the context
var customer = context.CommerceContext.GetObject(x => string.Equals(x.Id, arg.EntityId, StringComparison.CurrentCulture));
if (customer == null)
{
return Task.FromResult(arg);
}
// All the values will be on the properties object on the arg object
// Getting the submitted Excemption code property value
var taxExceptionCode = arg.Properties.FirstOrDefault(x =>x.Name.Equals("TaxExcemptionCode", StringComparison.OrdinalIgnoreCase))?.Value;
// Save the Exception Code for the customer in the datstode so that it can be retrieved later. You can save it to a database, using an entity store, as a component attached to the customer entity object or anyway you like.
//SaveCustomerExemptionCode(customer.Id, taxExceptionCode);
// You can set a breakpoint on the line below so that you can check the value of taxExceptionCode above.
// Return control to the pipeline.
return Task.FromResult(arg);
}
}
When done, we need to open the "ConfigureSitecore.cs" class under the plugin and add code to chain EditCustomerTaxExemptCodeBlock to the "IDoActionPipeline" pipeline so that it will be called when an action submits value or invokes a command.
We will configure it by adding the code below.
.ConfigurePipeline(c =>
{
c.Add().After();
})
After adding it, it becomes
public void ConfigureServices(IServiceCollection services)
{
var assembly = Assembly.GetExecutingAssembly();
services.RegisterAllPipelineBlocks(assembly);
services.Sitecore().Pipelines(config => config
.ConfigurePipeline(c =>
{
c.Add().After();
})
.ConfigurePipeline(c =>
{
c.Add().After();
})
.ConfigurePipeline(c =>
{
c.Add().After();
})
.ConfigurePipeline(configure => configure.Add()));
services.RegisterAllCommands(assembly);
}
Now lets test out the feature.
Deploy the code and go to business tools,
open any customer profile and click on the edit icon, fill in a value and submit. Your value should be saved in your data store and would be immediately retrieved for display by the "GetCustomerTaxExemptCodeViewBlock".
You can set a breakpoint on the last line of EditCustomerTaxExemptCodeBlock class and see the value of the taxExcemptionCode variable.
Happy coding.
If you have any questions or require any help with a plugin you are developing, simply reach out to us at Xcentium and we will be happy to help.