Part 3: All about Sitecore Serialization using Sitecore CLI

Series of Sitecore CLI blogs

You can check if you have the serialization plugin:

> dotnet sitecore plugin list

If plugin is not there, then install it by running:

> dotnet sitecore plugin add -n Sitecore.DevEx.Extensibility.Serialization

To get the information about serialization commands run:

> dotnet sitecore ser -h

You will get all the information about parameters like below:

Once setup of Sitecore CLI and Sitecore content serialization configuration is done, you are good to execute serialization of items.

First, login via interactive or non interactive flow(check link for more details):

Here, will go with non-interactive client login:

> dotnet sitecore login --authority https://<sitecore-identity-server> --cm http://<sitecore-instance> --allow-write true --client-credentials true --client-id <client-id> --client-secret <client-secret>

Login information will be saved in .sitecore/user.json.

Pull:

Serialize items from Sitecore to disk

You can figure out usage of individual commands with -h or –help command.

  • Pull all items as configured in *.module.json.
> dotnet sitecore ser pull

After pulling items, you will see .yml file corresponding to each item in sitecore in disk

  • Pull items from specific module only(with tags separated by comma ,For inserting tags, check – link)
> dotnet sitecore ser pull -i tags:[tagA,tagB]
  • Pull items from specific module only(with Module namespace)
> dotnet sitecore ser pull -i ModuleA ModuleB
  • Pull all items except certain modules with tags:
> dotnet sitecore ser pull -e tags:[tagA,tagB]
  • Pull all items except some modules(with Module namespace)
> dotnet sitecore ser pull -e ModuleA ModuleB
  • Pull items without integrity validation
> dotnet sitecore ser pull -s
  • Check the differences/changes in disk and Sitecore without actually pulling it
> dotnet sitecore ser pull --what-if

Validate

Ensures file system integrity of serialized items in disk and their paths

It performs the following checks on serialized items:

  • Invalid physical path.
  • Orphaned parent ID.
  • Non-included item.
  • Empty folder.
  • Duplicate item ID.
  • Non-unique path.

  • Run the validation on serialized items
> dotnet sitecore ser validate
  • Fix the issues found in validation above
> dotnet sitecore ser validate --fix

If the --fix command finds duplicate content items in your file system, it keeps the last updated one and deletes the oldest one.

Package

  • Create package of serialized items in disk
> dotnet sitecore ser pkg create -o FILE_PATH

Package with extension .itempackage will be created at the file path given in command.

Create package at root path location

> dotnet sitecore ser pkg create -o <Package name>
  • Install the created package in Sitecore
> dotnet sitecore ser pkg install -f FILE_PATH

Push

Serialize items from disk to Sitecore

Push commands can be executed just like pull commands.

  • Push all items as configured in *.module.json.
> dotnet sitecore ser push
  • Push items from specific module only(with tags separated by comma , for inserting tags, check – link)
> dotnet sitecore ser push -i tags:[tagA,tagB]
  • Push items from specific module only(with Module namespace)
> dotnet sitecore ser push -i ModuleA ModuleB
  • Push all items except certain modules with tags:
> dotnet sitecore ser push -e tags:[tagA,tagB]
  • Push all items except some modules(with Module namespace)
> dotnet sitecore ser push -e ModuleA ModuleB
  • Push items without integrity validation
> dotnet sitecore ser push -s
  • Check the differences/changes in disk and Sitecore without actually pushing it
> dotnet sitecore ser push --what-if

Explain

To check if specific path is present in any modules.
> dotnet sitecore ser explain --path "PATH"

It will return the module information in which mentioned “path” is present.

Watch

Pull the changed items automatically into file system.
  • Activate watch
> dotnet sitecore ser watch
Advertisement

Part 2: Sitecore Content Serialization Configuration(Sitecore CLI)

Series of Sitecore CLI blogs

After this, you will get following folders and files:

.sitecore folder will have schemas and user.json file.

schemas folder contains three schemas:

  • ModuleFile schema for each module configuration (<module name>.module.json)
  • RootConfiguration required in setting up sitecore.json
  • UserConfiguration schema used in user.json when we login into sitecore instance with interactive or non-interactive flow.

User.json saves the login information once the user logged in successfully via interactive or non-interactive flow. Do not commit this file in source control.

Configure sitecore.json

This file will be common to the project. We can configure following information in sitecore.json.

  • RootConfigurationFile.schema.json schema is added at the top
  • Modules: Add path of module or you can add a wildcard as given above to include all modules by itself based on architecture of project.
  • Plugins: plugins installed in this for serialization, index, publish, resource packaging and database are added here.
  • Serialization: It contains information about allowed max relative path length, module relative serialization path(where item ymls will be created), should it continue if serialization fails at some item, what fields need be excluded, etc.

There are several fields on items which do not contain important information and not required to be serialized. So you can add an array of the excluded fields with two properties: fieldId and description to Exclude property.

For Example:

"excludedFields": [
      {
        "fieldId": "badd9cf9-53e0-4d0c-bcc0-2d784c282f6a",
        "description": "__Updated by"
      }
]

NOTE:

-Excluding Revision fields from serialization can result in issues when publishing with the serialization command.
-You can also mention excluded fields in specific *module.json files to exclude from particular module items only.

Settings: It contains boolean entries to enable/disable telemetry and to enable the feature to check the Sitecore management services version compatibility.

Configure <module name>.module.json

  • Create module file with <module name>.module.json.
  • Add the Module schema at the top.

Update the path as per location of file.

  • After schema, add following:
{
  "namespace": "",
  "references": [""],
  "items": ""
}
From dev.sitecore.com
  • In items property, mention sitecore item’s include paths to sync items, descendants, children, etc. as given below:
items:"
"includes": [
        {
          "name": "<name>",
          "path": "<sitecore item path>",
          "scope": "<scope>",
          "allowedPushOperations": "<allowedPushOperations>"
          "rules": [
                    {
                        "path": "<sitecore item path>",
                        "scope": "<scope>"
                    }
        }
]"

Details about include properties:

From dev.sitecore.com
  • Order of the include paths is important. For example: templates, layouts, renderings, should be added first in include path list to avoid conflicts while syncing content items due to dependency on templates, layouts, etc.
  • You can also create a separate Base.module.json file for base items like templates, branches etc. and the add it as references(refer below code snippet) in rest of files. So in this way, we can order include paths inside the *.module.json and also set the hierarchy in which all module files need to be serialized.

  • You can set the relative path at the module level(refer below code snippet) inside items. This will override the defaultModuleRelativeSerializationPath given in sitecore.json.

  • You can also set the different exclude fields in different modules rather than setting same exclude fields for all modules.
...
items": {
        "includes": [
            {
                "name": "Apikey",
                "path": "/sitecore/system/Settings/Services/API Keys"
            },
            {
                "name": "Media",
                "path": "/sitecore/media library/my-first-jss-app"
            }
        ],
        "excludedFields":[
            {
	          "fieldID": "{EB504D1B-B612-4FFF-B239CA3BD7273D1B}",
		  "description": "FieldsForExclude1"
	    },
            {
		  "fieldID": "{3C2C061E-F61F-4DF6-89EA-0B7A56348737}",
		  "description": "FieldsForExclude2"
	     }
        ]
    }
  • Like the include paths of content items, you can exclude certain set of items with rules along with each include path. For example:
"items": {
  "includes": [
    {
      "name": "content",
      "path": "/sitecore/content/home",
      "rules": [
        {
           "path": "/products/legacy",
           "scope": "ignored"
        },
        {
           "path": "/products",
           "scope": "ItemAndDescendants",
           "allowedPushOperations": "createUpdateAndDelete"
        },
        {
           "path": "*",
           "scope": "ignored"
        }
      ]
    }
  ]
}

Details about rule properties:

From dev.sitecore.com

NOTE:

- Scope: Ignored, is only valid in configuring rules
- Sitecore CLI does not support duplicate item names and will break push/pull operations
  • In the initial serialization, you will require to sync everything. However, in proceeding serialization, you may require to serialize a module or couple of modules. You can mention tags in the individual module files and then use those tags with push and pull commands to sync more targeted changes rather than to sync all

Pull command with tags:

dotnet sitecore ser pull --include tags:[global-208]
  • You can also serialize role by adding roles property in module.json. The roles property is an array that consists of role predicate items with two properties: domain (Sitecore role domain) and pattern (a regex pattern to determine specific roles to include under the domain). For example:
{
    ...
    "items": {
        ...
    },
    "roles": [
      {
        "domain": "sitecore",
        "pattern": "Developer"
      },
      {
        "domain": "custom",
        "pattern": "Role*"
      },
      {
        "domain": "extranet",
        "pattern": "^MySite.*$"
      }
    ]
}

Part 1: Getting started with Sitecore CLI

Series of Sitecore CLI blogs

Following are the steps to install and use Sitecore CLI for your project:

Pre requisites:

Install  .NET Core 

Sitecore Instance

Working Sitecore identity

Steps to install:

  • Install Sitecore Management services package(few dlls and configs) on your sitecore instance. Make sure to check compatibility from here.
  • Open Powershell in admin mode
  • Run following commands in your root directory:

> dotnet new tool-manifest
> dotnet nuget add source -n Sitecore https://sitecore.myget.org/F/sc-packages/api/v3/index.json
> dotnet tool install Sitecore.CLI 
Use the -g option when running the install command to install CLI globally. However, it is not recommended because different instances may need different version of Sitecore CLI
  • Initialize a new project
> dotnet sitecore init
  • Install the required Publishing and Serialization plugins
> dotnet sitecore plugin add -n Sitecore.DevEx.Extensibility.Serialization
> dotnet sitecore plugin add -n Sitecore.DevEx.Extensibility.Publishing

You can check the list of plugins installed by using following command:

> dotnet sitecore plugin list

After this, your folder will look like following:

To verify the sitecore CLI is successfully installed and working(-h for help)

> dotnet sitecore -h

Login to Sitecore with Sitecore CLI:

Sitecore CLI supports two flows of authentication and authorization.

  • An interactive user login, using a device code flow. Please follow below steps for interactive login:
> dotnet sitecore login --authority https://<sitecore-identity-server> --cm https://<sitecore-instance> --allow-write true

This will navigate you to browser to authorize and modify ~/.sitecore/user.json with access tokens. Make sure not to commit user.json as it contains sensitive information. After this, you are good to serialize items.

  • A non-interactive client login, using a client credentials flow. This is used by clients such as Continuous Integration servers. Please follow below steps for non- interactive login:

Make configuration changes:

create a file named Sitecore.IdentityServer.DevEx.xml and add following:

<?xml version="1.0" encoding="utf-8"?>
<Settings>
  <Sitecore>
    <IdentityServer>
      <Clients>
        <!-- used to authenticate servers with client id and client secret -->
        <CliServerClient>
            <ClientId>SitecoreCLIServer</ClientId>
            <ClientName>SitecoreCLIServer</ClientName>
            <AccessTokenType>0</AccessTokenType>
            <AccessTokenLifetimeInSeconds>3600</AccessTokenLifetimeInSeconds>
            <IdentityTokenLifetimeInSeconds>3600</IdentityTokenLifetimeInSeconds>
            <RequireClientSecret>true</RequireClientSecret>
            <AllowOfflineAccess>false</AllowOfflineAccess>
            <AllowedGrantTypes>
                <!--
                    client_credentials authenticates with client ID and client secret
                    which is good for CI, tools, etc. However, it's not tied to a USER,
                    it's tied to a client ID.
                -->
                <AllowedGrantType1>client_credentials</AllowedGrantType1>
            </AllowedGrantTypes>
            <ClientSecrets>
                <!--<ClientSecret1>SUPERLONGSECRETHERE</ClientSecret1>-->
            </ClientSecrets>
            <AllowedScopes>
                <!-- this is required even if not a 'user' for Sitecore to like us -->
                <AllowedScope1>sitecore.profile.api</AllowedScope1>
            </AllowedScopes>
        </CliServerClient>
      </Clients>
    </IdentityServer>
  </Sitecore>
</Settings>

Add client id and client secret(max length = 100) in this file. Keep this file in Config folder of Sitecore Identity Server.

create another file Sitecore.Owin.Authentication.ClientCredentialsMapping.
config
 containing the following:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
  <sitecore role:require="Standalone or ContentDelivery or ContentManagement">
    <federatedAuthentication>
      <identityProviders>
        <identityProvider id="SitecoreIdentityServer" type="Sitecore.Owin.Authentication.IdentityServer.IdentityServerProvider, Sitecore.Owin.Authentication.IdentityServer" resolve="true">
          <transformations hint="list:AddTransformation">
            <transformation name="admin-ify client credentials users" type="Sitecore.Owin.Authentication.Services.DefaultTransformation, Sitecore.Owin.Authentication">
              <sources hint="raw:AddSource">
                <claim name="client_id" value="SitecoreCLIServer" />
              </sources>
              <targets hint="raw:AddTarget">
                <claim name="name" value="sitecore\superuser" />
                <claim name="http://www.sitecore.net/identity/claims/isAdmin" value="true" />
				<claim name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" value="" />

              </targets>
              <keepSource>true</keepSource>
            </transformation>
          </transformations>
          
        </identityProvider>
      </identityProviders>
    </federatedAuthentication>
  </sitecore>
</configuration>

Add email id(same client id as first file) in email address claim value. And keep it in App_Config/Include/ folder of Sitecore Content management server.

Do the IIS Reset.

Open powershell in admin mode.

Now login:

> dotnet sitecore login --authority https://<sitecore-identity-server> --cm http://<sitecore-instance> --allow-write true --client-credentials true --client-id <client-id> --client-secret <client-secret>

Login information will be saved in .sitecore/user.json

Next step would be Sitecore serialization configuration- structure, rules and items pulled depends decisions taken in defining the configurations.

How you want the items to be pulled? you want to opt for site wise folders, component wise folders, page wise folders, etc. What fields should be excluded while performing pull and push of items?

Everything depends on how you plan to do Sitecore serialization configuration. Please find the details in next blog.

Sitecore CLI login without Identity server

Find the listener by running dotnet Sitecore.IdentityServer.Host.dll in Identity server root folder:

Now login with listener got in above step: https://localhost:5000 or http://localhost:5000 as authority parameter (bypassing identity server) in login command:

dotnet sitecore login --authority https://<sitecore-identity-server> --cm https://<sitecore-instance> --allow-write true

HTTP Error 500.19 – Internal Server Error on identity server

In our QA environment, Sitecore login page is always navigating to ?fbc=1 and bypassing the identity server. When I hit the identity server url directly, it gave me 500.19 error (above screenshot).

First, I started checking the event logs for errors and troubleshooting. As I am having Sitecore version 10.2, so I checked for all the pre-requisite required for it to verify anything is missing.

After some troubleshooting, I figured out that there is problem with IIS support, .NET core, .NET core runtime and URL rewrite. So, installing latest version of .NET core windows hosting bundle and URL rewrite 2.1 resolved the issue.

For more information on HTTP Error 500.19, check out https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/iis/health-diagnostic-performance/http-error-500-19-webpage

Configure the editing host for local XM Cloud

After setting up my local environment, when I tried to open item in experience editor, it gave me below error:

Unable to connect to the remove server

In order to resolve this, we need to connect to rendering host using items because the solution is based on Sitecore Experience Accelerator (SXA)

App Name:

Make sure that name of the app in settings item under your site and app name(config.appName) mentioned in src/<app-folder>/package.json file of the front-end app are same.

Predefined application rendering host:

Check if the Predefined application rendering host field has the value ‘default’ in /content/<SiteName>/<AppName>/Settings/Site Grouping/<your-site> item

Application name:

Verify that the application name is correct in /System/Settings/Services/Rendering Hosts/Default.

If not, change it to match the config.appName setting in the package.json file of your rendering app.

If the field is left blank, then the field uses, by default, the value you configured in step 1(App Name).

Local Configuration:

Create an rendering host item under /System/Settings/Services/Rendering Hosts/ and add values.

In the Server side rendering engine endpoint URL field, enter the URL to the front-end app’s API route for rendering in editing mode.( check docker-compose.override.yml file in the root of your project)

In the Server side rendering engine application URL field, enter the value of the RENDERING_HOST_INTERNAL_URI environment variable.(check docker-compose.override.yml file in the root of your project)

In the Application name field, enter the name of your front-end application as configured in the package.json file.

Select local predefined application rendering host

In the /content/<sitename>/<appname>/Settings/Site Grouping/<appname> item, in the Settings section, the Predefined application rendering host field, select the Local rendering host definition and save the item

Run front-end app in connected mode:

Go to your rendering host directory (say sxastarter) and start the front-end application in connected mode with the command:

npm run start:connected

Now, Open any item in experience editor

Getting started with XM Cloud(Local)

In this blog, I will give a walkthrough how to setup local XM cloud development environment.

Pre requisites:

  1. A valid Sitecore license file
  2. Windows PowerShell 5.1. (PowerShell 7 is not supported at this time)
  3. The current long-term support (LTS) version of Node.js
  4. .NET Core 6.0 SDK
  5. .NET Framework 4.8 SDK
  6. Visual Studio 2022
  7. Docker for Windows, with Windows Containers enabled(Make sure you have all components for running containers/docker)

Prepare Local Environment to run containers:

Open Powershell in admin mode.

Make sure that Internet Information not running at port 443:

Get-Process -Id (Get-NetTCPConnection -LocalPort 443).OwningProcess

If you found any, stop IIS:

iisreset /stop

Check if you have Apache Solr or any other service running on port 8984:

Get-Process -Id (Get-NetTCPConnection -LocalPort 8984).OwningProcess

If yes, stop it:

Stop-Service -Name "<the name of your service>"

or

nssm stop "<the name of your service>"

Set up the XM cloud development solution

The starter template has scripts for following:

Clone the repository you configured for the XM Cloud project and open powershell in admin mode in same folder and run below commands:

Prepare the Sitecore Container environment:

.\init.ps1 -InitEnv -LicenseXmlPath "<C:\path\to\license.xml>" -AdminPassword "<desired password>"

Restart terminal or VS code after this as instructed.

Now, run up.ps1 to download the Sitecore Docker images, install and configure the containers and client application

.\up.ps1

Do as instructed in your browser while running this script, log in to the Sitecore XM instance and accept the device authorization.

Following screenshot has the images created:

The starter XM cloud template is just empty instance

Now, Configure Item serialization to synchronize items between environments

Create a serialization json file under /src for syncing templates, renderings, content items, media items etc. Follow the below syntax:

{
  "name": "<name of type of data>",
  "path": "<sitecore item path>",
  "allowedPushOperations": "CreateUpdateAndDelete"
}

I created a Headless site and configured content tree, media, templates in *.module.json file for serialization.

In order to serialize items, execute the following:

Authorize the local project in your XM Cloud organization

dotnet sitecore cloud login

Do as instructed to log in, authorize your device and all this connectivity information is stored in the .sitecore/user.json file.

Connect to the local environment

dotnet sitecore connect --ref xmcloud --cm https://xmcloudcm.localhost --allow-write true -n local

To pull serialized items from the remote XM Cloud environment

dotnet sitecore ser pull -n "local"

To push the serialized items into your locally running XM instance

dotnet sitecore ser push -n "local"

Configure the editing host

After this, you are all set to use your local XM cloud environment. In this next blog, we will connect this local environment with remote environment.

How to fix – “A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 – Error Locating Server/Instance Specified)”

Recently I installed new Sitecore XP instance and when I have created tenant folder and tenant, I saw warning related to license on the top which says current license does not support SXA.

So I switched the license which supports SXA. And then yellow page with above error pops up.

Resolution:

Fix the certificate value in the connection string file in your wwroot folder. This has resolved the issue for me.

You can check learn.microsoft.com for more insights into the issue and troubleshooting ways.

How to know 2Ws of last executed PowerShell script in Sitecore

Does your team need to run scripts for adding, deleting or updating bulk content in sitecore?

Are you working with team of many resources, multiple environment and working remotely?

If any of the above is true for you, then at times you may also faced issues when some powershell script execution went into wrong direction and whole team just suffered/stuck after that.

If working with big teams and large amount of work in queue, it may become difficult to reach out to all and spend time to figure out cause and then resolve the solution. We do get information from the logs about script ran in past. However, it is not really wise to spend much time to troubleshoot a problem.

……………………………………………………………………………………………………………………………………………………………………………………………..

Sitecore has out of the box ISE in system folder to see WHO has ran the script previously in Sitecore and moreover it let us know WHAT exactly was last executed. This really helps to expedite the troubleshooting process.

You will find this at location – /sitecore/system/Modules/PowerShell/Settings/ISE (refer below image)

By default, Save last executed script checkbox is enabled.

Hope you find it useful!

Integrate Sitecore form with Sitecore Send to send data

In this blog, I will give you a walkthrough on how we can connect Sitecore form to our new marketing automation friend Sitecore send to submit data.

Create a Sitecore send account before going ahead. You can choose from different plans available with different features at different prices or you can also use the trail/free version for one month to explore. However, all features are not available in the free version for one month.

If you already have Sitecore send account. Lets start by following below steps:

1. Create a Email List in Sitecore send

Create a Email List from email list option available under audience menu option(refer below image). It is really easy to create and add members with just few clicks. By default, there are Name, Mobile and Email fields for each member in Email list. Email is the mandatory field here. You can also add custom fields if required and perform computations. We will discuss that in another blog.

2. Create a Sitecore form

Create a Sitecore form with email as mandatory field, name and few other fields if required(refer below for an example)

For more details, refer https://doc.sitecore.com/xp/en/users/93/sitecore-experience-platform/creating-forms.html

3. Create a Sitecore custom submit action

a. Create a submit action item under /sitecore/system/Settings/Forms/Submit Actions and add the Submit Action class to the model type field.

b. Then, add the custom submit action to the form’s submit button.

c. Create a custom submit action class

First copy the IDs of email list and Sitecore Send API Key from Sitecore Send(refer below images) that we will use while making a call to Sitecore send:

API Key:

Email List ID

d. Now inherit the SubmitActionBase and override Execute method to add a code snippet by which a POST request is made to the Uri(refer below code) asynchronously. You will get a Boolean response from the API call about status.

using Newtonsoft.Json;
using Sitecore.Diagnostics;
using Sitecore.ExperienceForms.Models;
using Sitecore.ExperienceForms.Mvc.Models.Fields;
using Sitecore.ExperienceForms.Processing;
using Sitecore.ExperienceForms.Processing.Actions;
using System;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using static Demo.Foundation.SitecoreExtensions.Models.SitecoreSendModel;

namespace Demo.Foundation.SitecoreExtensions.Actions
{
    public partial class SitecoreSend : SubmitActionBase<string>
    {
        private readonly Uri baseAddress = new Uri("https://api.moosend.com/v3/");
        private readonly string emailListID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"; // Add email list ID here, refer above images
        private readonly string SitecoreSendApiKey = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx"; // Add sitecore send api key here, refer above images

        public SitecoreSend(ISubmitActionData submitActionData) : base(submitActionData)
        {
        }

        protected override bool TryParse(string value, out string target)
        {
            target = string.Empty;
            return true;
        }

        protected override bool Execute(string data, FormSubmitContext formSubmitContext)
        {
            Assert.ArgumentNotNull(formSubmitContext, "formSubmitContext");
			var txtFirstName = GetFieldValue(this.GetValue(formSubmitContext.Fields.FirstOrDefault(f => f.Name.Equals("FirstName"))));
            var txtLastName = GetFieldValue(this.GetValue(formSubmitContext.Fields.FirstOrDefault(f => f.Name.Equals("LastName"))));
			var emailAddress = GetFieldValue(this.GetValue(formSubmitContext.Fields.FirstOrDefault(f => f.Name.Equals("Email"))));
            var mobile= GetFieldValue(this.GetValue(formSubmitContext.Fields.FirstOrDefault(f => f.Name.Equals("Mobile"))));
            var SitecoreSendModel = new SitecoreSendData
            {
                Name = txtFirstName +" "+ txtLastName,
                Email = emailAddress,
                Mobile = mobile
            };
            Task<bool> executePost = Task.Run(async () => await ExecuteCall(JsonConvert.SerializeObject(SitecoreSendData)));
            return executePost.Result;
        }

        private async Task<bool> ExecuteCall(string data)
        {
            try
            {
                using (var httpClient = new HttpClient { BaseAddress = baseAddress })
                {
                    httpClient.DefaultRequestHeaders.TryAddWithoutValidation("accept", "application/json");

                    using (var content = new StringContent(data, Encoding.Default, "application/json"))
                    {
                        using (var response = await httpClient.PostAsync($"subscribers/{emailListID}/subscribe.json?apiKey={SitecoreSendApiKey}", content))
                        {
                            string response = await response.Content.ReadAsStringAsync();
							if(!string.IsNullOrEmpty(response))
							{
								return true;
							}
							else
							{
								return false;
							}
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Log.Error(ex.Message.ToString(), "ExecuteCall");
                return false;
            }
        }

        private string GetFieldValue(IViewModel field)
        {
                if (field != null && field is ListViewModel)
				{
					PropertyInfo property = field.GetType().GetProperty("Value");
                    string str = (object)property != null ? property.GetValue(field, (object[])null)?.ToString() : (string)null;
                    return str;
			}
        }
    }
    public partial class SitecoreSendModel
    {
            public string Name { get; set; }
            public string Email { get; set; }
            public string Mobile { get; set; }
    }
}

For more details, refer https://doc.sitecore.com/xp/en/developers/90/sitecore-experience-manager/walkthrough–creating-a-custom-submit-action.html

4. Submit form

After submitting the data successfully, you will be able to see that the one with source as API integration are submissions via API from Sitecore form and others were done manually into email list.

Hope you like this blog! 🙂