Creating custom storefront APIs in the backend often involves a time-consuming process of crafting multiple files. Our PowerShell script automates endpoint creation to streamline this, generating the necessary classes and interfaces effortlessly. Let’s explore in this blog how to automate endpoint creation in Optimizely Configured Commerce with PowerShell.
The Endpoints
Optimizely Configured Commerce interacts with the Configured Commerce data through RESTful services.
Based on the feature/functionality’s requirements, there is always a need to create custom storefront APIs in Optimizely Commerce-based projects to post/get/update the specific data.
There is a specific flow of code execution that occurs from the client-side request to the server side and back to the client-side in response.
This HTTP request typically includes information about the action being performed, such as requested resources, parameters, headers, etc.
Whenever any page loads on the browser, it renders out its associated widgets having endpoints which could be responsible for retrieving product information, order processing, managing the shopping cart or billing, etc.
The Flow
These endpoints / APIs maps to their respective controllers in the backend, and the request goes to the specific controller method along with the client-side request object which holds the client-side parameters.
E.g., The endpoint “api/v1/products/{productId}” maps to one of the ProductsV1Controller.Get methods and returns data for a specific product.
HTTP Request routes to the respective controller class’s action method.
Through the controller method, the code execution goes to the Mapper class’s MapParameter() method for mapping client-side request object parameter with server-side parameter class along with query string e.g. filter and expander.
After parameter mapping, execution goes to the service class where based on the server-side parameter and result classes, the execute() method of the actual handler class executes and returns the server-side result
Again the execution goes to the Mapper class and executes the MapResult() method where all the server-side result parameter values get mapped with client-side object parameters.
Finally, the execution goes back to the controller method and the result model to the client browser.
Creating a single endpoint involves generating approximately 10 files containing classes and interfaces in the backend, requiring considerable manual effort.
The Implementation
Below are the files we need to create manually to create an endpoint.
ApiModels
Model.cs
Parameter.cs
Controller
Controller.cs
Services
IService.cs
Service.cs
Services/Parameters
ServiceParameter.cs
Services/Results
Result.cs
Mappers
IMapper.cs
Mapper.cs
Handlers
Handler.cs
Hence, to reduce manual intervention and simplify the creation of new endpoints, we’ve created a PowerShell utility that needs an endpoint name and path where we need to create a new API, when running the script, it auto-creates the entire 10 files in the backend with sample code for the API.
Later, a developer can extend the code in the classes like client-side request parameter, response, mapper, service, service parameter, result, handler, and controller as per their need.
To Automate Endpoints in Optimizely Configured Commerce with PowerShell Script
$apiName = Read-Host “Enter the API Name”
if ($apiName -eq “”) {
Write-Host “You did not enter the API Name!”
exit
}
$apiPath = Read-Host “Enter the API Path”
if ($apiPath -eq “”) {
Write-Host “You did not enter the API Path!”
exit
}
#Handlers
$handlerFolder = $apiPath+”Handlers”
if (!(Test-Path -Path $handlerFolder -PathType Container)){ New-Item -Path $handlerFolder -ItemType Directory }
$handlerNamespace = $handlerFolder -split [regex]::Escape(“src”)
$handlerNamespace = $handlerNamespace[1] -replace [regex]::Escape(“”), “.”
$handlerNamespace = $handlerNamespace -replace [regex]::Escape(“..”), “.”
$handlerFolder = $handlerFolder + “” + $apiName+”Handler.cs”
$serviceNamespace = $handlerNamespace -replace [regex]::Escape(“Handler”), “Services.Parameter”
$resultNamespace = $handlerNamespace -replace [regex]::Escape(“Handler”), “Services.Result”
$handlerName = $apiName + “Handler”
$serviceParameter = $apiName + “ServiceParameter”
$serviceResult = $apiName + “Result”
$csHandlerContent = @”
using Insite.Core.Interfaces.Dependency;
using Insite.Core.Services.Handlers;
using Insite.Core.Interfaces.Data;
using System;
using $serviceNamespace;
using $resultNamespace;
namespace $handlerNamespace
{
[DependencyName(“$apiName”)]
public sealed class $handlerName
: HandlerBase<$serviceParameter, $serviceResult>
{
public override int Order => 100;
public override $serviceResult Execute(IUnitOfWork unitOfWork, $serviceParameter parameter, $serviceResult result)
{
result.SampleProperty = “This the sample proerpty!”;
return this.NextHandler.Execute(unitOfWork, parameter, result);
}
}
}
“@
#ApiModels
$apiModelsFolder = $apiPath+”ApiModels”
if (!(Test-Path -Path $apiModelsFolder -PathType Container)){ New-Item -Path $apiModelsFolder -ItemType Directory }
$apiModelsNamespace = $apiModelsFolder -split [regex]::Escape(“src”)
$apiModelsNamespace = $apiModelsNamespace[1] -replace [regex]::Escape(“”), “.”
$apiModelsNamespace = $apiModelsNamespace -replace [regex]::Escape(“..”), “.”
$apiModelsFolderModel = $apiModelsFolder + “” + $apiName+”Model.cs”
$apiModelsFolderParameter = $apiModelsFolder + “” + $apiName+”Parameter.cs”
$apiModelName = $apiName + “Model”
$lApiModelName = $apiModelName.Substring(0, 1).ToLower() + $apiModelName.Substring(1)
$apiParameterName = $apiName + “Parameter”
$lApiParameterName = $apiParameterName.Substring(0, 1).ToLower() + $apiParameterName.Substring(1)
$modelContent = @”
using Insite.Core.Plugins.Search.Dtos;
using Insite.Core.WebApi;
using Newtonsoft.Json;
using System.Collections.Generic;
namespace $apiModelsNamespace
{
public class $apiModelName : BaseModel
{
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public PaginationModel Pagination { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string SampleProperty { get; set; }
}
}
“@
$parameterContent = @”
using Insite.Core.WebApi;
using System;
using System.Collections.Generic;
namespace $apiModelsNamespace
{
public class $apiParameterName : BaseParameter
{
public string SampleProperty { get; set; }
public int? Page { get; internal set; }
}
}
“@
#Services
$servicesFolder = $apiPath+”Services”
if (!(Test-Path -Path $servicesFolder -PathType Container)){ New-Item -Path $servicesFolder -ItemType Directory }
$servicesNamespace = $servicesFolder -split [regex]::Escape(“src”)
$servicesNamespace = $servicesNamespace[1] -replace [regex]::Escape(“”), “.”
$servicesNamespace = $servicesNamespace -replace [regex]::Escape(“..”), “.”
$servicesFolderClass = $servicesFolder + “” + $apiName+”Service.cs”
$servicesFolderInterface = $servicesFolder + “I” + $apiName+”Service.cs”
$serviceName = $apiName + “Service”
$iServiceName = “I” + $serviceName
$serviceResultName = $apiName + “Result”
$serviceMethodName = “Get” + $apiName + “Collection”
$servicesParameterName = $apiName + “ServiceParameter”
$servicesParameterFolder = $apiPath+”ServicesParameters”
if (!(Test-Path -Path $servicesParameterFolder -PathType Container)){ New-Item -Path $servicesParameterFolder -ItemType Directory }
$servicesParametersNamespace = $servicesParameterFolder -split [regex]::Escape(“src”)
$servicesParametersNamespace = $servicesParametersNamespace[1] -replace [regex]::Escape(“”), “.”
$servicesParametersNamespace = $servicesParametersNamespace -replace [regex]::Escape(“..”), “.”
$servicesParameterFolder = $servicesParameterFolder + “” + $apiName+”ServiceParameter.cs”
$servicesResultFolder = $apiPath+”ServicesResults”
if (!(Test-Path -Path $servicesResultFolder -PathType Container)){ New-Item -Path $servicesResultFolder -ItemType Directory }
$servicesResultNamespace = $servicesResultFolder -split [regex]::Escape(“src”)
$servicesResultNamespace = $servicesResultNamespace[1] -replace [regex]::Escape(“”), “.”
$servicesResultNamespace = $servicesResultNamespace -replace [regex]::Escape(“..”), “.”
$servicesResultFolder = $servicesResultFolder + “” + $apiName+”Result.cs”
$iServiceContent = @”
using Insite.Core.Interfaces.Dependency;
using Insite.Core.Services;
using $servicesResultNamespace;
using $servicesParametersNamespace;
namespace $servicesNamespace
{
[DependencyInterceptable]
public interface $iServiceName :
IDependency,
ISettingsService
{
$serviceResultName $serviceMethodName($servicesParameterName parameter);
}
}
“@
$serviceContent = @”
using Insite.Core.Interfaces.Data;
using Insite.Core.Interfaces.Dependency;
using Insite.Core.Services;
using Insite.Core.Services.Handlers;
using $resultNamespace;
using System;
using $servicesParametersNamespace;
namespace $servicesNamespace
{
public class $serviceName :
ServiceBase,
$iServiceName,
IDependency,
ISettingsService
{
protected readonly IHandlerFactory HandlerFactory;
public $serviceName(IUnitOfWorkFactory unitOfWorkFactory, IHandlerFactory handlerFactory)
: base(unitOfWorkFactory)
{
this.HandlerFactory = handlerFactory;
}
[Transaction]
public $serviceResultName $serviceMethodName($servicesParameterName parameter)
{
$serviceResultName result = ($serviceResultName)null;
this.UnitOfWork.ExecuteWithoutChangeTracking((Action)(() => result = this.HandlerFactory.GetHandler<IHandler<$servicesParameterName, $serviceResultName>>().Execute(this.UnitOfWork, parameter, new $serviceResultName())));
return result;
}
}
}
“@
#Services/Parameters
$serviceParameterContent = @”
using Insite.Core.Context;
using Insite.Core.Extensions;
using Insite.Core.Interfaces.EnumTypes;
using Insite.Core.Plugins.Pricing;
using System;
using System.Collections.Generic;
using System.Linq;
using $apiModelsNamespace;
using Insite.Core.WebApi;
using Insite.Core.Services;
namespace $servicesParametersNamespace
{
public class $servicesParameterName : PagingParameterBase
{
public $servicesParameterName(
$apiParameterName $lApiParameterName)
{
if ($lApiParameterName == null)
return;
this.SampleProperty = $lApiParameterName.SampleProperty;
}
public string SampleProperty { get; set; }
}
}
“@
#Services/Result
$lServiceResultName = $serviceResultName.Substring(0, 1).ToLower() + $serviceResultName.Substring(1)
$serviceResultContent = @”
using Insite.Core.Plugins.Search.Dtos;
using Insite.Core.Services;
using Insite.Data.Entities;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace $servicesResultNamespace
{
public class $serviceResultName : PagingResultBase
{
public virtual string SampleProperty { get; set; }
public virtual ReadOnlyCollection<SortOrderDto> SortOptions { get; set; }
public virtual string SortOrder { get; set; }
public virtual bool ExactMatch { get; set; }
}
}
“@
#Mappers
$mapperFolder = $apiPath+”Mappers”
if (!(Test-Path -Path $mapperFolder -PathType Container)){ New-Item -Path $mapperFolder -ItemType Directory }
$mappersNamespace = $mapperFolder -split [regex]::Escape(“src”)
$mappersNamespace = $mappersNamespace[1] -replace [regex]::Escape(“”), “.”
$mappersNamespace = $mappersNamespace -replace [regex]::Escape(“..”), “.”
$mapperFolderClass = $mapperFolder + “” + $apiName+”Mapper.cs”
$mapperFolderInterface = $mapperFolder + “I” + $apiName+”Mapper.cs”
$mapperName = $apiName + “Mapper”
$imapperName = “I” + $mapperName
$iMapperContent = @”
using Insite.Core.Interfaces.Dependency;
using Insite.Core.WebApi.Interfaces;
using $apiModelsNamespace;
using $servicesNamespace;
using $servicesResultNamespace;
using $servicesParametersNamespace;
namespace $mappersNamespace
{
[DependencyInterceptable]
public interface $imapperName :
IWebApiMapper<$apiParameterName, $servicesParameterName, $serviceResultName, $apiModelName>,
IDependency,
IExtension
{
}
}
“@
$mapperContent = @”
using Insite.Core.Interfaces.Dependency;
using Insite.Core.Plugins.Search.Dtos;
using Insite.Core.Plugins.Utilities;
using Insite.Core.Services;
using Insite.Core.WebApi;
using Insite.Core.WebApi.Interfaces;
using Insite.Core.Extensions;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net.Http;
using $apiModelsNamespace;
using $servicesNamespace;
using $servicesResultNamespace;
using $servicesParametersNamespace;
namespace $mappersNamespace
{
public class $mapperName :
$imapperName,
IWebApiMapper<$apiParameterName, $servicesParameterName, $serviceResultName, $apiModelName>,
IDependency,
IExtension
{
public $mapperName(
IUrlHelper urlHelper,
IObjectToObjectMapper objectToObjectMapper)
{
}
public $servicesParameterName MapParameter($apiParameterName apiParameter, HttpRequestMessage request)
{
$servicesParameterName parameter = new $servicesParameterName(apiParameter);
if (apiParameter != null)
parameter.Page = new int?(apiParameter.Page ?? 1);
string queryString = request.GetQueryString(“filter”);
if (!queryString.IsBlank())
{
string[] source2 = queryString.ToLower().Split(‘,’);
parameter.PageSize = ((IEnumerable<string>)source2).Contains<string>(“pagesize”)?20:10;
}
return parameter;
}
public $apiModelName MapResult($serviceResultName serviceResult, HttpRequestMessage request)
{
$apiModelName $lApiModelName = new $apiModelName();
if (serviceResult != null)
{
$lApiModelName.SampleProperty = serviceResult.SampleProperty;
$lApiModelName.Pagination = this.MakePaging(request, serviceResult);
}
return $lApiModelName;
}
public virtual PaginationModel MakePaging(
HttpRequestMessage httpRequestMessage,
$serviceResultName $lServiceResultName)
{
PaginationModel paginationModel = new PaginationModel((PagingResultBase)$lServiceResultName);
if (paginationModel.NumberOfPages > 1 && paginationModel.Page < paginationModel.NumberOfPages)
{
var routeValues = new
{
page = paginationModel.Page + 1,
query = httpRequestMessage.GetQueryString(“query”),
pageSize = httpRequestMessage.GetQueryString(“pageSize”),
categoryId = httpRequestMessage.GetQueryString(“categoryId”),
sort = httpRequestMessage.GetQueryString(“sort”),
expand = httpRequestMessage.GetQueryString(“expand”)
};
}
if (paginationModel.Page > 1)
{
var routeValues = new
{
page = paginationModel.Page – 1,
query = httpRequestMessage.GetQueryString(“query”),
pageSize = paginationModel.PageSize,
categoryId = httpRequestMessage.GetQueryString(“categoryId”),
sort = httpRequestMessage.GetQueryString(“sort”),
expand = httpRequestMessage.GetQueryString(“expand”)
};
}
if ($lServiceResultName.SortOptions != null)
{
paginationModel.SortOptions = $lServiceResultName.SortOptions.Select<SortOrderDto, SortOptionModel>((Func<SortOrderDto, SortOptionModel>)(o => new SortOptionModel()
{
DisplayName = o.DisplayName,
SortType = o.SortType
})).ToList<SortOptionModel>();
paginationModel.SortType = $lServiceResultName.SortOrder;
}
return paginationModel;
}
}
}
“@
#Controllers
$controllerFolder = $apiPath+”Controller”
if (!(Test-Path -Path $controllerFolder -PathType Container)){ New-Item -Path $controllerFolder -ItemType Directory }
$controllerNamespace = $controllerFolder -split [regex]::Escape(“src”)
$controllerNamespace = $controllerNamespace[1] -replace [regex]::Escape(“”), “.”
$controllerNamespace = $controllerNamespace -replace [regex]::Escape(“..”), “.”
$controllerFolder = $controllerFolder + “” + $apiName+”V1Controller.cs”
$controllerName = $apiName + “Controller”
$lControllerName = $controllerName.Substring(0, 1).ToLower() + $controllerName.Substring(1)
$lMapperName = $mapperName.Substring(0, 1).ToLower() + $mapperName.Substring(1)
$lserviceName = $serviceName.Substring(0, 1).ToLower() + $serviceName.Substring(1)
$routeName = $apiName + “V1″
$csControllerContent = @”
using Insite.Core.Plugins.Utilities;
using Insite.Core.WebApi;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description;
using $servicesNamespace;
using $mappersNamespace;
using $apiModelsNamespace;
using $servicesResultNamespace;
using $servicesParametersNamespace;
using System;
namespace $controllerNamespace
{
[RoutePrefix(“api/v1/$apiName”)]
public class $controllerName : BaseApiController
{
private readonly $imapperName $lMapperName;
private readonly $iserviceName $lServiceName;
public $controllerName(ICookieManager cookieManager,
$imapperName $lMapperName,
$iserviceName $lServiceName)
: base(cookieManager)
{
this.$lMapperName = $lMapperName;
this.$lServiceName = $lServiceName;
}
[Route(“”, Name = “$routeName”)]
[ResponseType(typeof($apiModelName))]
public async Task<IHttpActionResult> Get([FromUri] $apiParameterName model)
{
$controllerName $lControllerName = this;
return await $lControllerName.ExecuteAsync<$imapperName,
$apiParameterName,
$servicesParameterName,
$serviceResultName,
$apiModelName>($lControllerName.$lMapperName,
new Func<$servicesParameterName, $serviceResultName>
($lControllerName.$lServiceName.$serviceMethodName),
model);
}
}
}
“@
$csControllerContent | Set-Content -Path $controllerFolder
$csHandlerContent | Set-Content -Path $handlerFolder
$modelContent | Set-Content -Path $apiModelsFolderModel
$parameterContent | Set-Content -Path $apiModelsFolderParameter
$iServiceContent | Set-Content -Path $servicesFolderInterface
$serviceContent | Set-Content -Path $servicesFolderClass
$serviceParameterContent | Set-Content -Path $servicesParameterFolder
$serviceResultContent | Set-Content -Path $servicesResultFolder
$iMapperContent | Set-Content -Path $mapperFolderInterface
$mapperContent | Set-Content -Path $mapperFolderClass
You can access the utility script by downloading it from this GitHub repository: jitendrachilate16/Optimizely-Utility-Scripts (github.com)
The Demo
For the demo, we created an endpoint “GetSampleData” in the “Catalog” module using the script.
Step 1:
Execute the PowerShell script “CreateEndpoint.ps1”. PowerShell command prompt will ask to enter the name of the API.
Supply the endpoint name, as an example, we have furnished: GetSampleData.
Hit enter!
Step 2:
Following that, it prompts you to input the path where you wish to create the API.
In my case I’ve provided: C:InsiteOptimizelyB2BCommerce-mastersrcExtensionsCatalog
Hit enter!
Step 3:
Subsequently, navigate to the solution, verifying the creation of essential classes and interface files within the designated project and path. Then, proceed to build the solution.
Step 4:
After successfully building the solution, proceed to browse the API – and there you have it. Your API is now ready for use!
You can of course make necessary updates to extend your endpoint as per the requirement.
Please view the below gif to demonstrate its overall working.
Conclusion
In this blog, we explored a simplified way to create custom storefront APIs in Optimizely Configured Commerce. Instead of the usual manual effort, we introduced a handy PowerShell script that automates the process. The script generates all the necessary files with just a few inputs, making it much easier for developers. We walked through the web API request flow and even demonstrated the script’s use by creating an endpoint called “GetSampleData.” You can find and use the script on GitHub to simplify your endpoint creation and updates based on your project needs.
Thank you for your time! I believe the automation insights shared in this blog will prove to be a valuable time-saver.
Leave A Comment