allBlogsList

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<EntityView, EntityView, CommercePipelineExecutionContext>"  and implements its Abstract member which is a run method

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<EntityView, EntityView, CommercePipelineExecutionContext>
    {


        public GetCustomerTaxExemptCodeViewBlock(ViewCommander viewCommander)
        {
            _viewCommander = viewCommander;
        }

        private readonly ViewCommander _viewCommander;

        public override Task<EntityView> 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<knowncustomerviewspolicy>();

            // 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<IGetEntityViewPipeline>(c =>
                {
                    c.Add<GetCustomerTaxExemptCodeViewBlock>().After<GetCustomerDetailsViewBlock>();
                })

After adding it, it becomes

        public void ConfigureServices(IServiceCollection services)
        {
            var assembly = Assembly.GetExecutingAssembly();
            services.RegisterAllPipelineBlocks(assembly);

            services.Sitecore().Pipelines(config => config

                .ConfigurePipeline<IGetEntityViewPipeline>(c =>
                {
                    c.Add<GetCustomerTaxExemptCodeViewBlock>().After<GetCustomerDetailsViewBlock>();
                })

               .ConfigurePipeline<IConfigureServiceApiPipeline>(configure => configure.Add<ConfigureServiceApiBlock>()));

            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.

Inject pipelines in constructor

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<EntityView, EntityView, CommercePipelineExecutionContext>" and implements its Abstract member which is a Run method

The code below shows the completed class with comments for guidance.

    public class PopulateCustomerTaxExemptCodeActionBlock : PipelineBlock<EntityView, EntityView, CommercePipelineExecutionContext>
    {
        // Abstract member implementation.
        public override Task<EntityView> 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<actionspolicy>();

            // 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<IPopulateEntityViewActionsPipeline>(c =>
                {
                    c.Add<PopulateCustomerTaxExemptCodeActionBlock>().After<InitializeEntityViewActionsBlock>();
                })

After adding it, it becomes

        public void ConfigureServices(IServiceCollection services)
        {
            var assembly = Assembly.GetExecutingAssembly();
            services.RegisterAllPipelineBlocks(assembly);

            services.Sitecore().Pipelines(config => config

                .ConfigurePipeline<IGetEntityViewPipeline>(c =>
                {
                    c.Add<GetCustomerTaxExemptCodeViewBlock>().After<GetCustomerDetailsViewBlock>();
                })
                .ConfigurePipeline<IPopulateEntityViewActionsPipeline>(c =>
                {
                    c.Add<PopulateCustomerTaxExemptCodeActionBlock>().After<InitializeEntityViewActionsBlock>();
                })

               .ConfigurePipeline<IConfigureServiceApiPipeline>(configure => configure.Add<ConfigureServiceApiBlock>()));

            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.

Inject pipelines in constructor

Fig2

When we click the edit button, a form pops up for us to add the excemption code as shown in the figure below.

Inject pipelines in constructor

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<EntityView, EntityView, CommercePipelineExecutionContext>" and implements its Abstract member which is a Run method

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<EntityView, EntityView, CommercePipelineExecutionContext>
    {
        // Inject CommerceCommander into the constructor
        public EditCustomerTaxExemptCodeBlock(CommerceCommander commerceCommander)
        {
            _commerceCommander = commerceCommander;
        }

        private readonly CommerceCommander _commerceCommander;

        public override Task<EntityView> 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<Sitecore.Commerce.Plugin.Customers.Customer>(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<IDoActionPipeline>(c =>
                {
                    c.Add<EditCustomerTaxExemptCodeBlock>().After<ValidateEntityVersionBlock>();
                })

After adding it, it becomes

        public void ConfigureServices(IServiceCollection services)
        {
            var assembly = Assembly.GetExecutingAssembly();
            services.RegisterAllPipelineBlocks(assembly);

            services.Sitecore().Pipelines(config => config

                .ConfigurePipeline<IGetEntityViewPipeline>(c =>
                {
                    c.Add<GetCustomerTaxExemptCodeViewBlock>().After<GetCustomerDetailsViewBlock>();
                })
                .ConfigurePipeline<IPopulateEntityViewActionsPipeline>(c =>
                {
                    c.Add<PopulateCustomerTaxExemptCodeActionBlock>().After<InitializeEntityViewActionsBlock>();
                })
                .ConfigurePipeline<IDoActionPipeline>(c =>
                {
                    c.Add<EditCustomerTaxExemptCodeBlock>().After<ValidateEntityVersionBlock>();
                })
               .ConfigurePipeline<IConfigureServiceApiPipeline>(configure => configure.Add<ConfigureServiceApiBlock>()));

            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.