Всем привет. Некоторое время назад стояла задача интегрировать виджет CDEK в сайт на .NET. Код виджета доступен на github: фронт (ts) + бэкенд (php). При переносе на .NET с фронтом проблем нет. Кроме того, есть готовый скрипт, который можно подключить с cdn. Но при этом есть существенное ограничение для бэкенда: данный скрипт работает только с php.
В данной статье показано, как перевести виджет на бэкенд .NET. При этом фронтенд код остается неизменным. Прежде, чем начать, хочу предупредить, что данное решение никак не связано с официальной версией виджета и не поддерживается командой CDEK. В любой момент CDEK может изменить свой код без сохранения обратной совместимости, и решение, представленное здесь, может перестать работать. Тем не менее, думаю (вернее, мне хотелось бы так думать 🙂 ), что информация, представленная в данной статье, может быть полезной как с точки зрения конечного результата, так и в образовательных целях.
По умолчанию фронт виджета посылает запросы на файл service.php (оригинальный код), находящийся в корне сайта. Поэтому задача в конечном счете свелась к двум подзадачам:
-
добавить имитацию service.php для запросов с фронта в .NET сайт
-
перевести код service.php на .NET
Первая подзадача решается достаточно просто через добавление промежуточного слоя (middleware) для запросов на service.php. В типовом ASP.NET Core приложении это можно сделать, добавив следующий код в Program.cs:
app.UseWhen( context => context.Request.Path.ToString().ToLower().Contains("/service.php"), appBranch => { appBranch.UseCdekMiddleware(); });
С данной конструкцией запросы на service.php будут обрабатываться нашим промежуточным слоем CdekMiddleware (обращаю внимание, что все это мы делаем внутри .NET сайта, не имеющим ничего общего с php. Т.е. php не нужно устанавливать на ваш сервер, где крутится .NET сайт). Соответственно код метода-расширения UseCdekMiddleware() и самого промежуточного слоя:
internal class CdekMiddleware { public CdekMiddleware(RequestDelegate next) { } public async Task Invoke(HttpContext context, ICdekService cdekService) { var requestParams = await this.getRequestParams(context); object action = null; requestParams?.TryGetValue("action", out action); if (string.IsNullOrEmpty(action as string)) { await this.sendValidationError(context, "Action is required"); return; } if (string.Compare(action as string, "offices", true) == 0) { var result = cdekService.GetOffices(requestParams); await this.writeResponseAsync(context, result); return; } if (string.Compare(action as string, "calculate", true) == 0) { var calculations = cdekService.Calculate(requestParams); await this.writeResponseAsync(context, calculations); return; } await this.sendValidationError(context, "Unknown action"); } private async Task<Dictionary<string, object>> getRequestParams(HttpContext ctx) { var data = new Dictionary<string, object>(); foreach (var kv in ctx.Request.Query) { data[kv.Key] = kv.Value.ToString(); } var stream = ctx.Request.Body; string json = await new StreamReader(stream).ReadToEndAsync(); if (!string.IsNullOrEmpty(json)) { var body = JsonConvert.DeserializeObject<ExpandoObject>(json) as IDictionary<String, Object>; body?.ToList().ForEach(kv => { data[kv.Key] = kv.Value; }); } return data; } private async Task sendValidationError(HttpContext context, string msg) { context.Response.StatusCode = StatusCodes.Status400BadRequest; await this.writeResponseAsync(context, new CdekResponse{Data = msg}); } private async Task writeResponseAsync(HttpContext context, CdekResponse resp) { resp?.Headers?.ToList().ForEach(kv => { context.Response.Headers.TryAdd(kv.name, kv.value); }); await context.Response.WriteAsJsonAsync((object)resp?.Data); } } internal static class CdeknMiddlewareExtension { public static IApplicationBuilder UseCdekMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware<CdekMiddleware>(); } }
При переносе кода старался сделать его как можно ближе к оригиналу, используя мои (скромные) знания php. Как видно из анализа кода оригинального виджета, service.php по сути является прокси между фронтом и API CDEK, который работает в вашем домене, чтобы при подключении фронта не было проблем с CORS. Поэтому вместе непосредственно с данными в формате json, в ответ сервера также добавляются кастомные HTTP заголовки, пришедшие из API (заголовки, названия которых начинаются с X-). Так например виджет получает список пунктов вывоза с поддержкой постраничой навигации (используются заголовки X-Current-Page, X-Total-Elements, X-Total-Pages).
Внутри CdekMiddleware вызывает CdekService для получения списка пунктов вывоза и расчета стоимости доставки. Код CdekService вместе с рабочим тестовым приложением на .NET8 доступен на github. Инструкция для запуска приложения также найдете на github. Для запуска вам потребуюся clientId/clientSecret для API CDEK (тестовые ключи можно найти на сайте документации CDEK) и ключ API Яндекс Карт (необходимо сгенерировать в кабинете разработчика Яндекс).
Настройки виджета в приложении взяты из примера CDEK с указанием всех настроек виджета. Если все сделано правильно, то при запуске появится виджет с возможностью выбора пункта вывоза и расчета стоимости доставки:
ссылка на оригинал статьи https://habr.com/ru/articles/894802/
Добавить комментарий