Fermer

décembre 5, 2023

Automatisez la création de points de terminaison dans un commerce configuré de manière optimale avec PowerShell / Blogs / Perficient

Automatisez la création de points de terminaison dans un commerce configuré de manière optimale avec PowerShell / Blogs / Perficient


La création d’API de vitrine personnalisée dans le backend implique souvent un processus fastidieux de création de plusieurs fichiers. Notre script PowerShell automatise la création de points de terminaison pour rationaliser cela, en générant sans effort les classes et les interfaces nécessaires. Explorons dans ce blog comment automatiser la création de points de terminaison dans Commerce configuré de manière optimisée avec PowerShell.

Les points finaux

Commerce configuré de manière optimisée interagit avec les données de Commerce configuré via les services RESTful.

En fonction des exigences de la fonctionnalité/fonctionnalité, il est toujours nécessaire de créer des API de vitrine personnalisées dans les projets basés sur Optimizely Commerce pour publier/obtenir/mettre à jour les données spécifiques.

Il existe un flux spécifique d’exécution de code qui se produit depuis la requête côté client vers le côté serveur et de retour vers le côté client en réponse.

Cette requête HTTP inclut généralement des informations sur l’action en cours, telles que les ressources demandées, les paramètres, les en-têtes, etc.

Chaque fois qu’une page se charge sur le navigateur, elle affiche ses widgets associés ayant des points de terminaison qui pourraient être responsables de la récupération des informations sur le produit, du traitement des commandes, de la gestion du panier ou de la facturation, etc.

Le flux

Ces points de terminaison/API sont mappés à leurs contrôleurs respectifs dans le backend, et la requête est envoyée à la méthode de contrôleur spécifique avec l’objet de requête côté client qui contient les paramètres côté client.

Par exemple, le point de terminaison « api/v1/products/{productId} » correspond à l’une des méthodes ProductsV1Controller.Get et renvoie les données d’un produit spécifique.

  • La requête HTTP est acheminée vers la méthode d’action de la classe de contrôleur respective.
  • Grâce à la méthode du contrôleur, l’exécution du code est transmise à la méthode MapParameter() de la classe Mapper pour mapper le paramètre d’objet de requête côté client avec la classe de paramètres côté serveur ainsi que la chaîne de requête, par exemple un filtre et un extenseur.
  • Après le mappage des paramètres, l’exécution est dirigée vers la classe de service où, en fonction des classes de paramètres et de résultats côté serveur, la méthode execute() de la classe de gestionnaire réelle s’exécute et renvoie le résultat côté serveur.
  • Encore une fois, l’exécution va à la classe Mapper et exécute la méthode MapResult() où toutes les valeurs des paramètres de résultat côté serveur sont mappées avec les paramètres d’objet côté client.
  • Enfin, l’exécution remonte à la méthode du contrôleur et le modèle résultat au navigateur client.
Couler

Flux de requêtes d’API Web dans un commerce configuré de manière optimale

La création d’un seul point de terminaison implique de générer environ 10 fichiers contenant des classes et des interfaces dans le backend, ce qui nécessite un effort manuel considérable.

La mise en oeuvre

Vous trouverez ci-dessous les fichiers que nous devons créer manuellement pour créer un point de terminaison.

Modèles API

Modèle.cs

Paramètre.cs

Manette

Contrôleur.cs

Prestations de service

IService.cs

Service.cs

Services/Paramètres

ServiceParameter.cs

Prestations/Résultats

Résultat.cs

Mappeurs

IMapper.cs

Mappeur.cs

Gestionnaires

Gestionnaire.cs

Par conséquent, pour réduire les interventions manuelles et simplifier la création de nouveaux points de terminaison, nous avons créé un utilitaire PowerShell qui a besoin d’un nom de point de terminaison et d’un chemin où nous devons créer une nouvelle API. Lors de l’exécution du script, il crée automatiquement l’intégralité des 10 fichiers. dans le backend avec un exemple de code pour l’API.

Plus tard, un développeur peut étendre le code dans les classes telles que le paramètre de requête côté client, la réponse, le mappeur, le service, le paramètre de service, le résultat, le gestionnaire et le contrôleur selon ses besoins.

Pour automatiser les points de terminaison dans un commerce configuré de manière optimale avec un script PowerShell

$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+"\Services\Parameters"
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+"\Services\Results"
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

Vous pouvez accéder au script de l’utilitaire en le téléchargeant depuis ce référentiel GitHub : jitendrachilate16/Optimizely-Utility-Scripts (github.com)

La démo

Pour la démo, nous avons créé un point de terminaison « GetSampleData » dans le module « Catalog » à l’aide du script.

Étape 1:

Exécutez le script PowerShell « CreateEndpoint.ps1 ». L’invite de commande PowerShell vous demandera de saisir le nom de l’API.

Fournissez le nom du point de terminaison, à titre d’exemple, nous avons fourni : GetSampleData.

Appuyez sur Entrée !

Étape 2:

Ensuite, il vous invite à saisir le chemin où vous souhaitez créer l’API.

Dans mon cas, j’ai fourni : C:\Insite\OptimizelyB2BCommerce-master\src\Extensions\Catalog\

Appuyez sur Entrée !

Étape 3:

Ensuite, accédez à la solution, en vérifiant la création des classes essentielles et des fichiers d’interface dans le projet et le chemin désignés. Ensuite, procédez à la création de la solution.

Étape 4:

Après avoir créé avec succès la solution, parcourez l’API – et voilà. Votre API est maintenant prête à être utilisée !

Vous pouvez bien sûr effectuer les mises à jour nécessaires pour étendre votre point de terminaison selon les besoins.

Veuillez consulter le gif ci-dessous pour démontrer son fonctionnement global.

Créer un point de terminaison

Démo pour créer un point de terminaison à l’aide d’un script.

Conclusion

Dans ce blog, nous avons exploré une manière simplifiée de créer des API de vitrine personnalisées dans Optimizely Configured Commerce. Au lieu de l’effort manuel habituel, nous avons introduit un script PowerShell pratique qui automatise le processus. Le script génère tous les fichiers nécessaires avec seulement quelques entrées, ce qui facilite grandement la tâche des développeurs. Nous avons parcouru le flux de requêtes de l’API Web et avons même démontré l’utilisation du script en créant un point de terminaison appelé « GetSampleData ». Vous pouvez trouver et utiliser le script sur GitHub pour simplifier la création et les mises à jour de vos points de terminaison en fonction des besoins de votre projet.

Merci pour votre temps! Je pense que les informations sur l’automatisation partagées dans ce blog se révéleront un gain de temps précieux. 🙂






Source link

décembre 5, 2023