15.07.2020, 17:55 | #1 |
Участник
|
ax2012, JSON для WCF Service: binding="webHttpBinding"?
Уважаемые знатоки сервисов, WCF и AIF в аксапте.
на аксфоруме было несколько обсуждений как в Аксапте работать с JSON запросами. обсуждения сводились к сериализации-десериализации json-строки средствами X++. но точно известно, что: 1. сервисы в ax2012 построены на Майкрософтовском фреймворке WCF 2. WCF вполне работает с JSON на уровне протокола. В частности, см. https://docs.microsoft.com/en-us/dot...without-aspnet Другими словами, я ожидаю, что для Аксаптовских сервисов можно поменять настройку биндинга, например, на webHttpBinding и сам WCF будет разбираться с JSON или XML на основании заголовка content-type. А сервис внутри Аксапты получит вполне десериализованный объект (возможно со слабой типизацией). Цитата:
HTTP POST requests with a content-type of "application/json" are treated as JSON, and those with content-type that indicate XML (for example, "text/xml") are treated as XML.
Вопрос 2: behaviorConfiguration="serviceBehaviorConfiguration", "dataContractSerializer". Есть что почитать на эту тему? Расскажите как это работает в ax2012? см. также: https://docs.microsoft.com/en-us/dot.../nettcpbinding https://docs.microsoft.com/en-us/dot...webhttpbinding Последний раз редактировалось mazzy; 15.07.2020 в 18:37. |
|
|
За это сообщение автора поблагодарили: Raven Melancholic (5). |
17.07.2020, 23:14 | #2 |
Боец
|
есть в d365 класс formjsonserializer, вполне работоспособный и для ах2012. Сериализует контракт класс и обратно. А вот для 2009 пришлось потрудиться, т.к. не поддерживаются аттрибуты класса - но и это решаемо. В гугле написано, что эта фишка WCF там ещё в свой псевдо-объект заворачивает, так что врядли без кастылей сработает.
|
|
06.08.2020, 12:15 | #3 |
Участник
|
Пробовал работать с jsonserializer в AX2012. Не всякий JSON им можно разобрать, особенно если в JSON имеются вложенные структуры с одинаковыми названиями JSONObject, но несущие в себе разные сущности. Конечно же проблема в лютой реализации некоторых JSON API с которыми приходилось работать.
|
|
06.08.2020, 13:05 | #4 |
Участник
|
Для полноценной работы с форматом JSON, используйте прослойку в виде ASP.NET WEB API прилложения, развёрнутого на IIS, которое занимается перегонкой JSON <-> WCF и объединением различных сервисов DAX, так же оно является промежуточным секюрным слоем, можно добавить кастомную систему авторизации, отфильтровать спам и кривые запросы, сообщить о недоступности сервисов DAX, обработать всякие ситуации, с которыми в DAX порой разобраться будет невозможно. Хорошим плюсом так же является возможность автоматического документирования web API сервиса (используем Swagger).
Стоит не забывать о том, что для работы с WCF DAX необходима авторизация. Для некоторых методов API используется стандартный QueryService, с возможностью постраничного запроса данных. Для некоторых моментов можно использовать метаданные AX так же через стандартный сервис MetadataService. В общем одни плюсы. В целом всё получается достаточно красиво при правильной организации проекта, примеры вызова: Код: /// <summary> /// Методы клиента (покупателя) /// </summary> [RoutePrefix("api/Sales/v1")] [ShowInSwagger] public class CustomersController : ApiController { private CallContext context = ClientFactory.CreateContext<CallContext>(); /// <summary> /// Список клиентов /// </summary> /// <param name="page">Страница</param> /// <param name="pageSize">Количество записей на странице</param> [HttpGet] [Route("Customers")] [SwaggerResponse(HttpStatusCode.OK, Description = "Список клиентов", Type = typeof(IEnumerable<Models.Sales.Customer.CustAccount>))] [SwaggerResponse(HttpStatusCode.NotFound, Description = "По запросу ничего не найдено")] [SwaggerResponse(HttpStatusCode.BadRequest, Description = "Ошибка AIF/WCF", Type = typeof(AifFaultError))] [ResponseType(typeof(QueryResult))] [CommonExceptionFilters] public IHttpActionResult GetCustomers([FromUri] long page = 1, int pageSize = 50) { if (page <= 0 || pageSize <= 0) return BadRequest(); return new Queries.Customer.CustomerListQuery(Request, new Models.Common.PaginationMetadata(page, pageSize)); } /// <summary> /// Информация о клиенте /// </summary> /// <param name="CustAccount">Счёт (код клиента)</param> [HttpGet] [Route("Customers/{CustAccount}")] [SwaggerResponse(HttpStatusCode.OK, Description = "Информация о клиенте", Type = typeof(Models.Sales.Customer.CustomerInfo))] [SwaggerResponse(HttpStatusCode.NotFound, Description = "По запросу ничего не найдено")] [SwaggerResponse(HttpStatusCode.BadRequest, Description = "Ошибка AIF/WCF", Type = typeof(AifFaultError))] [CommonExceptionFilters] public async Task<IHttpActionResult> GetCustomerInfo([FromUri] string CustAccount) { Models.Sales.Customer.CustomerInfo result; using (var client = ClientFactory.CreateClient<WebCustServicesClient>()) { var request = await client.GetCustomerAsync(context, CustAccount); if (request.response == null) return NotFound(); result = new Models.Sales.Customer.CustomerInfo { CustAccount = request.response.CustAccount, CustName = request.response.CustName, WebEmail = request.response.WebEmail, InventLocation = request.response.InventLocation, PersonnelNumber = request.response.PersonnelNumber, RouteId = request.response.RouteId, SegmentId = request.response.SegmentId, Settings = new Models.Sales.Customer.CustSettings() { AccountStatement = request.response.CustSettings.AccountStatement.ToCustAccountStatement(), SalesLineRejectionSendEmail = request.response.CustSettings.SalesLineRejectionSendEmail.ToNoYes() } }; } return Ok(result); } /// <summary> /// Баланс клиента /// </summary> /// <param name="CustAccount">Счёт (код клиента)</param> /// <param name="cur">Валюта</param> [HttpGet] [Route("Customers/{CustAccount}/Balance")] [SwaggerResponse(HttpStatusCode.OK, Description = "Баланс клиента", Type = typeof(Models.Sales.Customer.CustBalance))] [SwaggerResponse(HttpStatusCode.NotFound, Description = "По запросу ничего не найдено")] [SwaggerResponse(HttpStatusCode.BadRequest, Description = "Ошибка AIF/WCF", Type = typeof(AifFaultError))] [CommonExceptionFilters] public async Task<IHttpActionResult> GetCustBalance([FromUri] string CustAccount,string cur = "") { Models.Sales.Customer.CustBalance result; using (var client = ClientFactory.CreateClient<WebCustServicesClient>()) { var request = await client.GetCustBalanceAsync(context, CustAccount,cur); result = new Models.Sales.Customer.CustBalance() { CustAccount = request.response.CustAccount, Currency = request.response.CustCurrencyCode, Balance = request.response.Balance, ReservAmount = request.response.ReservAmount, SalesOpenAmount = request.response.SalesOpenAmount }; } return Ok(result); } .... Код: else if (exceptionType == typeof(ActionNotSupportedException)) { AifFaultError error = new AifFaultError(); error.Infolog.Add(new InfoMsg() { Type = Enum.GetName(typeof(InfologMessageType), InfologMessageType.Error), Message = "Метод WCF сервиса не поддерживается. Возможно он был изменён или переименован в AX." }); status = HttpStatusCode.BadRequest; message = new ObjectContent<AifFaultError>(error, new JsonMediaTypeFormatter()); } else { AifFaultError error = new AifFaultError(); error.Infolog.Add(new InfoMsg() { Type = Enum.GetName(typeof(InfologMessageType), InfologMessageType.Error), Message = actionExecutedContext.Exception.Message }); message = new ObjectContent<AifFaultError>(error, new JsonMediaTypeFormatter()); } |
|
21.10.2021, 11:12 | #5 |
Участник
|
Цитата:
Сообщение от DSPIC
есть в d365 класс formjsonserializer, вполне работоспособный и для ах2012. Сериализует контракт класс и обратно. А вот для 2009 пришлось потрудиться, т.к. не поддерживаются аттрибуты класса - но и это решаемо. В гугле написано, что эта фишка WCF там ещё в свой псевдо-объект заворачивает, так что врядли без кастылей сработает.
Не могли бы подробнее рассказать про методику использования этого функционала именно в DAX2009? Проблема в том, что от внешнего API в DAX2009 получаем строку JSON-формата (формат фиксирован), парсим её и обрабатываем уже множество JSON-объектов. Причём объекты JSON могут быть вложены друг в друга неограниченное количество раз (при обработке объектов JSON используется рекурсия). Всё работает, но не быстро (особенно для крупных JSON-объектов). Возможна ли оптимизация за счёт создания структуры классов, соответствующих структуре получаемого JSON, и прямая десериализация?
__________________
MS Dynamics AX 2009 Kernel 5.0.1600.4110 Application 5.0.1500.6491 |
|
21.10.2021, 16:33 | #6 |
Участник
|
Десериализации в X++ класс в версии AX2009 нет.
Но можно использовать .net и библиотеку Newtonsoft.Json http://axgrind.azurewebsites.net/201...d-JSON-Parsing Последний раз редактировалось S.Kuskov; 21.10.2021 в 16:38. |
|
|
За это сообщение автора поблагодарили: Sergey Petrov (1). |
22.10.2021, 01:13 | #7 |
Боец
|
Вот готовый проект сериализации\десериализации JSON\XML, на базе Newtonsoft.Json. Попробуйте, сравните. Как раз реализуется концепция "за счёт создания структуры классов, соответствующих структуре получаемого JSON, и прямая десериализация". Но скорость обработки от этого не зависит.
Ремарки: - В части JSON это в бОльшей степени даунгрейд класса FormJsonSerializer из D365 - В части XML написано с нуля - Всё это реализовано в разгар событий августа 2020 в Беларуси, под шум взрывающихся гранат и стрельбы, поэтому есть огрехи и частности в коде. Вылизывать уже не было сил. - Принцип использования как в AX2012\D365\.Net: создаём класс-контракт (можно вложенные), проставляем аттрибуты на методах, которые необходимо сериализоывать. Уровень вложенности значения не имеет. - Вместо аттрибутов, которые AX2009 нативно не поддерживает, используются специального вида макрос, тут картинка Вот так вызываем (в проекте есть джоб) X++: static void AXSerializerTutorial_JSON_XML(Args _args) { VendTable vendTable; DCVendor dcVendor; DCVendors dcVendors; Counter idx; str json, xml; ; dcVendors = DCVendors::construct(); while select vendTable order by RecId desc { dcVendor = DCVendor::constructVendTable(vendTable); dcVendors.parmVendors().addEnd(dcVendor); idx ++; if (idx == 3) { break; } } dcVendors.parmVendors(dcVendors.parmVendors()); // powinno być tak ;) json = dcVendors.serialize(HTTPRequestContentType::Json); xml = dcVendors.serialize(HTTPRequestContentType::XML); info(json); info(xml); dcVendors = dcVendors.deserialize(json, HTTPRequestContentType::Json); // dcVendors = dcVendors.deserialize(xml, HTTPRequestContentType::XML); // something went wrong... to be debugged } Получаем XML Да, прицел был больше на JSON, поэтому XMLная часть не особо отлаживалась - Я уже вижу на картинке не совсем верные наименования узлов + получил взлёт при десиарелизации. Наврное мелочь, но это не точно В общем попробуйте - расскажите по скорости и в целом, как оно. SharedProject_AXSerializer_JSON_XML.zip |
|
|
За это сообщение автора поблагодарили: mazzy (5), trud (10), raz (5), sukhanchik (6), Ace of Database (10), vmoskalenko (6), Sergey Petrov (1), S.Kuskov (10). |
23.10.2021, 17:18 | #8 |
Участник
|
Цитата:
Сообщение от S.Kuskov
Десериализации в X++ класс в версии AX2009 нет.
Но можно использовать .net и библиотеку Newtonsoft.Json http://axgrind.azurewebsites.net/201...d-JSON-Parsing Пока остановился на обработке самих объектов JSON, которые предоставляет Newtonsoft. Планирую ради эксперимента реализовать десериализацию и сравнить скорость при разборе JSON и при обработке структуры десериализованных классов.
__________________
MS Dynamics AX 2009 Kernel 5.0.1600.4110 Application 5.0.1500.6491 |
|
26.10.2021, 17:49 | #9 |
Участник
|
Прикрутил к нашей задаче. По времени почти то же самое. Чуть быстрее. Но структура и читабельность кода улучшилась на порядок. Так что, будем использовать предложенную методику.
Всем спасибо!
__________________
MS Dynamics AX 2009 Kernel 5.0.1600.4110 Application 5.0.1500.6491 |
|
Теги |
aif, ax2012, json, services, wcf |
|
|