|
|
#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). | |
|
|
#2 |
|
Боец
|
есть в d365 класс formjsonserializer, вполне работоспособный и для ах2012. Сериализует контракт класс и обратно. А вот для 2009 пришлось потрудиться, т.к. не поддерживаются аттрибуты класса - но и это решаемо. В гугле написано, что эта фишка WCF там ещё в свой псевдо-объект заворачивает, так что врядли без кастылей сработает.
|
|
|
|
|
#3 |
|
Участник
|
Цитата:
Конечно же проблема в лютой реализации некоторых JSON API с которыми приходилось работать.
|
|
|
|
|
#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());
} |
|
|
|
|
#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 |
|
|
|
|
#6 |
|
Участник
|
Десериализации в X++ класс в версии AX2009 нет.
Но можно использовать .net и библиотеку Newtonsoft.Json http://axgrind.azurewebsites.net/201...d-JSON-Parsing Последний раз редактировалось S.Kuskov; 21.10.2021 в 16:38. |
|
|
|
| За это сообщение автора поблагодарили: Sergey Petrov (1). | |
|
|
#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). | |
|
|
#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 |
|
|
|
|
#9 |
|
Участник
|
Прикрутил к нашей задаче. По времени почти то же самое. Чуть быстрее. Но структура и читабельность кода улучшилась на порядок. Так что, будем использовать предложенную методику.
Всем спасибо!
__________________
MS Dynamics AX 2009 Kernel 5.0.1600.4110 Application 5.0.1500.6491 |
|
|
| Теги |
| aif, ax2012, json, services, wcf |
|
|
|