07.07.2020, 16:49 | #1 |
Administrator
|
D365FO: Создание сервиса по шагам
Добрый день! Решил обновить информацию AX 2012 Создание сервиса по шагам но уже для D365FO.
Изменений не так много в части кода на Х++ и радикально много в части кода на C#. Все проверялось на версии платформы Update35 (7.0.5644.35548) и продукта 10.0.11 (10.0.464.10002) Итак, задача: 1. Получить по коду клиента его полное наименование и группу 2. Получить по группе клиентов перечень всех клиентов, входящих в эту группу. Карточка клиента в перечне определяется полями: a. Код клиента (CustTable.AccountNum)Начинаем. 1. Создаем класс Tutorial_CustServiceContract, который будет хранить нашу карточку клиента из трех полей с parm-методами доступа к них (public переменные, объявленные на уровне класса из C# недоступны. Однако в данном примере я сознательно сделал переменные класса public, чтобы из X++ можно было с ними работать напрямую, без использования parm-методов). Класс помечаем атрибутом DataContractAttribute, а методы – DataMemberAttribute. X++: /// <summary> /// Класс-контракт для хранения данных для сервиса по клиентам /// </summary> // VSUH, 30.06.2020 [DataContractAttribute] class Tutorial_CustServiceContract { public CustAccount custAccount; public DirPartyName custAccountName; public CustGroupId custGroupId; [DataMemberAttribute] public CustAccount parmCustAccount(CustAccount _custAccount = CustAccount) { custAccount = _custAccount; return custAccount; } [DataMemberAttribute] public DirPartyName parmCustAccountName(DirPartyName _custAccountName = custAccountName) { custAccountName = _custAccountName; return custAccountName; } [DataMemberAttribute] public CustGroupId parmCustGroupId(CustGroupId _custGroupId = custGroupId) { custGroupId = _custGroupId; return custGroupId; } } X++: /// <summary> /// Класс-сервис для вызова снаружи /// </summary> // VSUH, 30.06.2020 class Tutorial_CustService { /// <summary> /// Получение деталей карточки клиента по коду клиента /// </summary> /// <param name = "_custAccount"> /// Код клиента /// </param> /// <returns> /// Класс-контракт с деталями карточки клиента /// </returns> public Tutorial_CustServiceContract getCustDetail(CustAccount _custAccount) { CustTable custTable; Tutorial_CustServiceContract contract; custTable = CustTable::find(_custAccount); contract = new Tutorial_CustServiceContract(); contract.custAccount = _custAccount; contract.custAccountName = custTable.name(); contract.custGroupId = custTable.CustGroup; return contract; } /// <summary> /// Получение перечня клиентов из заданной группы /// </summary> /// <param name = "_custGroupId"> /// Код группы клиентов /// </param> /// <returns> /// Список классов-контрактов с деталями найденных клиентов /// </returns> [AifCollectionTypeAttribute('return', Types::Class, classstr(Tutorial_CustServiceContract))] public List getCustListOfGroup(CustGroupId _custGroupId) { CustTable custTable; Tutorial_CustServiceContract contract; List contractList; contractList = new List(Types::Class); while select custTable where custTable.CustGroup == _custGroupId { contract = new Tutorial_CustServiceContract(); contract.custAccount = custTable.AccountNum; contract.custAccountName = custTable.name(); contract.custGroupId = custTable.CustGroup; contractList.addEnd(contract); } return contractList; } } Class = Tutorial_CustService (связываем сервис с классом Tutorial_CustService) External Name = Tutorial_LabCustService - под этим именем сервис будет доступен снаружи через SOAP-технологию. 4. Добавляем в сервис операции - наши методы getCustDetail и getCustListOfGroup. У обоих методов включаем свойство Enable Idempotence = Yes (это означает, что при повторном вызове этих методов с теми же параметрами будет выдан тот же результат. Пока не исследовал влияние этого свойства на функциональность работы веб-сервисов). Свойство Subscriber access level отвечает за необходимый доступ к операциям сервиса внешней системы. Я это свойство оставил, как Read по умолчанию, т.к. в моем примере не производится изменение данных. Логично это свойство изменить на Invoke, если веб-сервис предполагает изменение данных в системе. 5. Сервис нужно включить в Service Group, которая будет публиковаться. Поэтому создаем Service Group Tutorial_CustServiceGroup, устанавливаем у нее свойство AutoDeploy = Yes (иначе она не опубликуется) и включаем в эту группу наш сервис Tutorial_CustService 6. Делаем билд. Собственно, всё. Х++-ная часть на этом закончилась . Веб-сервисы теперь доступны снаружи через технологию SOAP и технологию JSON. Технология SOAP предполагает, что потребитель веб-сервиса получает от него объекты и работает с этими объектами. Технология JSON предполагает, что потребитель веб-сервиса получает некую текстовую информацию, с которой сам разбирается (в т.ч. сам должен создавать у себя необходимые объекты, например - ту же карточку клиента). Поэтому JSON работает быстрее, но сложнее для программиста, а SOAP - удобнее для разработки. Соответственно SOAP-сервис доступен по адресу https://<URL>/soap/services/Tutorial_CustServiceGroup, где <URL> - это URL нашей системы; на OneBox это usnconeboxax1aos.cloud.onebox.dynamics.com, а Tutorial_CustServiceGroup - название нашей Service Group Задав в браузере в адресе такую строку мы получаем ответ Такой ответ говорит нам, что сервис успешно развернут и можно им пользоваться. JSON-сервис доступен по адресу https://<URL>/api/services/Tutorial_CustServiceGroup, где <URL> - это URL нашей системы; на OneBox это usnconeboxax1aos.cloud.onebox.dynamics.com, а Tutorial_CustServiceGroup - название нашей Service Group Задав в браузере в адресе такую строку мы получаем ответ Такой ответ говорит нам, что сервис успешно развернут и можно им пользоваться. Для JSON можно получить перечень всех веб-сервисов просто по адресу https://<URL>/api/services/, а также можно получить формат данных конкретного сервиса https://<URL>/api/services/Tutorial_CustServiceGroup/Tutorial_CustService и каждой его операции (https://<URL>/api/services/Tutorial_CustServiceGroup/Tutorial_CustService/getCustDetail и https://<URL>/api/services/Tutorial_CustServiceGroup/Tutorial_CustService/getCustListOfGroup) Официальные (от Microsoft) примеры-заготовки по использованию сервиса через SOAP / JSON расположены по адресу: https://github.com/Microsoft/Dynamic...erviceSamples/
__________________
Возможно сделать все. Вопрос времени Последний раз редактировалось sukhanchik; 08.07.2020 в 00:16. |
|
|
За это сообщение автора поблагодарили: raz (10), Logger (10), f18 (2), Stitch_MS (11), IvanS (1). |
07.07.2020, 17:54 | #2 |
Administrator
|
D365FO: Аутенфикация при использовании веб-сервиса
Независимо от применяемой технологии SOAP / JSON отдельной задачей стоит аутенфикация внешнего приложения при подключении к D365FO.
Для локальной (On premise) версии аутенфикация осуществляется через логин / пароль, которые проверяются службой ADFS (Active Directory Federation Services) Для облачной (Cloud) версии аутенфикация осуществляется через токен, т.е. большую текстовую строку, которая возвращается в приложение службой аутенфикации. Токен вычисляется на основе 4-х параметров:
Образец кода для аутенфикации лежит в https://github.com/microsoft/Dynamic...OAuthHelper.cs в методе GetAuthenticationHeader. X++: using Microsoft.IdentityModel.Clients.ActiveDirectory; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AuthenticationUtility { public class OAuthHelper { /// <summary> /// The header to use for OAuth authentication. /// </summary> public const string OAuthHeader = "Authorization"; /// <summary> /// Retrieves an authentication header from the service. /// </summary> /// <returns>The authentication header for the Web API call.</returns> public static string GetAuthenticationHeader(bool useWebAppAuthentication = false) { string aadTenant = ClientConfiguration.Default.ActiveDirectoryTenant; string aadClientAppId = ClientConfiguration.Default.ActiveDirectoryClientAppId; string aadClientAppSecret = ClientConfiguration.Default.ActiveDirectoryClientAppSecret; string aadResource = ClientConfiguration.Default.ActiveDirectoryResource; AuthenticationContext authenticationContext = new AuthenticationContext(aadTenant, false); AuthenticationResult authenticationResult; if (useWebAppAuthentication) { if (string.IsNullOrEmpty(aadClientAppSecret)) { Console.WriteLine("Please fill AAD application secret in ClientConfiguration if you choose authentication by the application."); throw new Exception("Failed OAuth by empty application secret."); } try { // OAuth through application by application id and application secret. var creadential = new ClientCredential(aadClientAppId, aadClientAppSecret); authenticationResult = authenticationContext.AcquireTokenAsync(aadResource, creadential).Result; } catch (Exception ex) { Console.WriteLine(string.Format("Failed to authenticate with AAD by application with exception {0} and the stack trace {1}", ex.ToString(), ex.StackTrace)); throw new Exception("Failed to authenticate with AAD by application."); } } else { // OAuth through username and password. string username = ClientConfiguration.Default.UserName; string password = ClientConfiguration.Default.Password; if (string.IsNullOrEmpty(password)) { Console.WriteLine("Please fill user password in ClientConfiguration if you choose authentication by the credential."); throw new Exception("Failed OAuth by empty password."); } try { // Get token object var userCredential = new UserPasswordCredential(username, password); ; authenticationResult = authenticationContext.AcquireTokenAsync(aadResource, aadClientAppId, userCredential).Result; } catch (Exception ex) { Console.WriteLine(string.Format("Failed to authenticate with AAD by the credential with exception {0} and the stack trace {1}", ex.ToString(), ex.StackTrace)); throw new Exception("Failed to authenticate with AAD by the credential."); } } // Create and get JWT token return authenticationResult.CreateAuthorizationHeader(); } } } X++: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AuthenticationUtility { public partial class ClientConfiguration { public static ClientConfiguration Default { get { return ClientConfiguration.OneBox; } } public static ClientConfiguration OneBox = new ClientConfiguration() { // You only need to populate this section if you are logging on via a native app. For Service to Service scenarios in which you e.g. use a service principal you don't need that. UriString = "https://usnconeboxax1aos.cloud.onebox.dynamics.com/", UserName = "tusr1@TAEOfficial.ccsctp.net", // Insert the correct password here for the actual test. Password = "", // You need this only if you logon via service principal using a client secret. See: [url]https://docs.microsoft.com/en-us/dynamics365/unified-operations/dev-itpro/data-entities/services-home-page[/url] to get more data on how to populate those fields. // You can find that under AAD in the azure portal ActiveDirectoryResource = "https://usnconeboxax1aos.cloud.onebox.dynamics.com", // Don't have a trailing "/". Note: Some of the sample code handles that issue. ActiveDirectoryTenant = "https://login.windows-ppe.net/TAEOfficial.ccsctp.net", // Some samples: [url]https://login.windows.net/yourtenant.onmicrosoft.com[/url], [url]https://login.windows.net/microsoft.com[/url] ActiveDirectoryClientAppId = "d8a9a121-b463-41f6-a86c-041272bdb340", // Insert here the application secret when authenticate with AAD by the application ActiveDirectoryClientAppSecret = "", // Change TLS version of HTTP request from the client here // Ex: TLSVersion = "1.2" // Leave it empty if want to use the default version TLSVersion = "", }; public string TLSVersion { get; set; } public string UriString { get; set; } public string UserName { get; set; } public string Password { get; set; } public string ActiveDirectoryResource { get; set; } public String ActiveDirectoryTenant { get; set; } public String ActiveDirectoryClientAppId { get; set; } public string ActiveDirectoryClientAppSecret { get; set; } } } Я изначально предполагаю, что имеется виртуалка OneBox, пользователь Admin которой сопоставлен с какой-нибудь учетной записью типа vasya@ivan-petrov.ru и есть возможность (в т.ч. права) под какой-нибудь учетной записью из домена @ivan-petrov.ru сделать настройки в Azure Active Directory. Итак, заходим на https://portal.azure.com/ и выбираем ярлык Azure Active Directory Слева выбираем пункт Регистрация приложений, жмем кнопку Новая регистрация и указываем имя приложения. Впоследствии мы свяжем данное приложение с пользователем, под которым внешнее приложение будет подключаться к нашему экземпляру системы D365FO, поэтому я указываю в качестве названия приложения - название нашего веб-сервиса Больше ничего указывать не обязательно, поэтому можно нажать на кнопку Зарегистрировать. После этого у нас откроется страничка, где уже будет нашему приложению присвоен Идентификатор приложения, он же Application Id (AppId), который мы впоследствии укажем в "четверке" параметров подключения в поле AppId Далее этому приложению нужно добавить секретный идентификатор, тот - который у нас будет указан в поле AppSecretId. Для этого, не уходя со странички приложения выбираем слева пункт "Сертификаты и секреты" И нажимаем кнопку "Новый секрет клиента" Указываем описание (любое) и срок действия - 1, 2 года или бессрочно и жмем кнопку Добавить Мы получаем секретный идентификатор, который впоследствии и нужно будет подставить в поле AppSecret. Его желательно сразу сохранить, т.к. потом на этой странице он уже отображаться не будет и его придется заново генерировать (в то время, как идентификатор приложения можно будет увидеть при повторном открытии страницы). Теперь нам нужно ассоциировать наше приложение с пользователем в D365FO. Для этого нужно зайти в систему в модуль Администрирование и выбрать пункт меню Настройка - Приложения Azure Active Directory И настроить соответствие между идентификатором приложения (AppId, код клиента) и пользователем системы (пользователь может и должен быть ограничен в правах) Дело осталось за малым - подготовить проект на C#, используя заготовки Microsoft и полученные идентификаторы AppId и AppSecret. На всякий случай отмечу, что в данном примере у меня получились следующие значения: Цитата:
AppId: "d4a93f6d-1d46-4e33-a258-3aa3ae7cb819"
AppSecretId: "J6Z86h8.JhXxYH.43TM5Bap7~_UZ29wI_R" Tenant: "ivan-petrov.ru" Resource: "https://usnconeboxax1aos.cloud.onebox.dynamics.com" X++: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AuthenticationUtility { public partial class ClientConfiguration { public static ClientConfiguration Default { get { return ClientConfiguration.OneBox; } } public static ClientConfiguration OneBox = new ClientConfiguration() { // You only need to populate this section if you are logging on via a native app. For Service to Service scenarios in which you e.g. use a service principal you don't need that. UriString = "", UserName = "", // Insert the correct password here for the actual test. Password = "", // You need this only if you logon via service principal using a client secret. See: [url]https://docs.microsoft.com/en-us/dynamics365/unified-operations/dev-itpro/data-entities/services-home-page[/url] to get more data on how to populate those fields. // You can find that under AAD in the azure portal ActiveDirectoryResource = "https://usnconeboxax1aos.cloud.onebox.dynamics.com", // Don't have a trailing "/". Note: Some of the sample code handles that issue. ActiveDirectoryTenant = "https://login.windows.net/ivan-petrov.ru", // Some samples: [url]https://login.windows.net/yourtenant.onmicrosoft.com[/url], [url]https://login.windows.net/microsoft.com[/url] ActiveDirectoryClientAppId = "d4a93f6d-1d46-4e33-a258-3aa3ae7cb819", // Insert here the application secret when authenticate with AAD by the application ActiveDirectoryClientAppSecret = "J6Z86h8.JhXxYH.43TM5Bap7~_UZ29wI_R", // Change TLS version of HTTP request from the client here // Ex: TLSVersion = "1.2" // Leave it empty if want to use the default version TLSVersion = "", }; public string TLSVersion { get; set; } public string UriString { get; set; } public string UserName { get; set; } public string Password { get; set; } public string ActiveDirectoryResource { get; set; } public String ActiveDirectoryTenant { get; set; } public String ActiveDirectoryClientAppId { get; set; } public string ActiveDirectoryClientAppSecret { get; set; } } } - Отсутствием значений переменных UserName, Password и UriString - Заполненными значениями переменных ActiveDirectoryResource (URL нашей системы D365FO), ActiveDirectoryTenant (строка "https://login.windows.net/", к которой добавлен наш домен ivan-petrov.ru), ActiveDirectoryClientAppId и ActiveDirectoryClientAppSecret, полученных при регистрации приложения в Azure AD
__________________
Возможно сделать все. Вопрос времени Последний раз редактировалось sukhanchik; 08.07.2020 в 00:03. |
|
07.07.2020, 22:31 | #3 |
Administrator
|
D365FO: Использование веб-сервиса через SOAP
В качестве образца использования веб-сервиса можно взять файл https://github.com/microsoft/Dynamic...ion/Program.cs, однако в конечном счете все нужно будет собрать в один проект на C#, поэтому начинаем с того, что создаем проект на C# типа Console Application. Мы можем создать проект любого типа - главное, чтобы он мог бы быть напрямую запущен пользователем.
В созданном проекте для авторизации по токену будет не хватать ссылки на библиотеку Microsoft.IdentityModel.Clients.ActiveDirectory (см узел References) Поэтому эту библиотеку нужно добавить путем выполнения команды в Visual Studio: Tools - NuGet Package Manager - Package Manager Console Цитата:
Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory
Теперь в проект нужно добавить из заготовок от Microsoft следующие файлы: ClientConfiguration.cs (с правками по параметрам авторизации) OAuthHelper.cs (без правок) SoapHelper.cs (без правок) Также добавляем Service Reference указав путь к веб-сервису (и нажав кнопку Go), а также указав имя Tutorial_CustServiceReference, под которым веб-сервис будет представлен в коде Далее пишем вот такой вот класс Program.cs. Обращаю внимание, что parm-методы воспринимаются на C#, как свойства (property) класса, а перечень карточек клиента - как массив. X++: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.IdentityModel.Clients.ActiveDirectory; using AuthenticationUtility; using SoapUtility; using System.ServiceModel; using System.ServiceModel.Channels; namespace Tutorial_ConsumeServiceSOAP { class Program { public const string serviceGroupName = "Tutorial_CustServiceGroup"; public const string serviceName = "Tutorial_LabCustService"; static void Main(string[] args) { var aosUriString = ClientConfiguration.Default.ActiveDirectoryResource; string bearerKey; bearerKey = OAuthHelper.GetAuthenticationHeader(true); var serviceUriString = SoapUtility.SoapHelper.GetSoapServiceUriString(serviceGroupName, aosUriString); EndpointAddress endpointAddress = new System.ServiceModel.EndpointAddress(serviceUriString); var binding = SoapUtility.SoapHelper.GetBinding(); var client = new Tutorial_CustServiceReference.Tutorial_LabCustServiceClient(binding, endpointAddress); var channel = client.InnerChannel; Tutorial_CustServiceReference.CallContext refContext = new Tutorial_CustServiceReference.CallContext(); refContext.Company = "RUMF"; refContext.Language = "ru"; refContext.PartitionKey = "initial"; using (OperationContextScope operationContextScope = new OperationContextScope(channel)) { HttpRequestMessageProperty requestMessage = new HttpRequestMessageProperty(); requestMessage.Headers[OAuthHelper.OAuthHeader] = bearerKey; OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestMessage; Tutorial_CustServiceReference.Tutorial_CustServiceContract result; Tutorial_CustServiceReference.Tutorial_CustServiceContract[] colResult; result = ((Tutorial_CustServiceReference.Tutorial_LabCustService)channel).getCustDetail(new Tutorial_CustServiceReference.getCustDetail(refContext, "RUMF-000001")).result; Console.WriteLine(string.Format("{0} | {1} | {2}", result.parmCustAccount, result.parmCustAccountName, result.parmCustGroupId)); colResult = ((Tutorial_CustServiceReference.Tutorial_LabCustService)channel).getCustListOfGroup(new Tutorial_CustServiceReference.getCustListOfGroup(refContext, "Орг")).result; foreach (Tutorial_CustServiceReference.Tutorial_CustServiceContract iterator in colResult) { Console.WriteLine(string.Format("{0} | {1} | {2}", iterator.parmCustAccount, iterator.parmCustAccountName, iterator.parmCustGroupId)); } } Console.ReadLine(); } } } У меня (на моей базе) получается такой результат:
__________________
Возможно сделать все. Вопрос времени Последний раз редактировалось sukhanchik; 09.12.2020 в 08:55. |
|
07.07.2020, 23:17 | #4 |
Administrator
|
D365FO: Использование веб-сервиса через JSON
Для JSON структура построения кода несколько иная. Нам необходимо подготовить строку текста в некотором формате, затем вызвать сервис, передав ему эту строку, после чего получить ответ также в виде строки и разобрать полученную строку.
Тем не менее, нам также нужно создать проект на C# типа Console Application (или любого типа, который может быть запущен напрямую пользователем) Процедура аутенфикации такая же, поэтому также необходимо добавлять в проект ссылку на библиотеку Microsoft.IdentityModel.Clients.ActiveDirectory путем выполнения команды Цитата:
Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory
Строку можно готовить (сериализовать) и разбирать (десериализовать) как "ручками", так и при помощи библиотеки Newtonsoft.Json, которую аналогичным способом (как и Microsoft.IdentityModel.Clients.ActiveDirectory) можно добавить в Package Manager Console путем выполнения команды Цитата:
Install-Package Newtonsoft.Json
ClientConfiguration.cs (с правками по параметрам авторизации) OAuthHelper.cs (без правок) А теперь начинаются различия. Для начала нам понадобится класс JsonUtil, который будет генерировать нам URL-адрес, добавляя в него группу сервисов, сам сервис и операции сервиса. X++: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Tutorial_ConsumeServiceJSON { public class JsonUtil { public const string OAuthHeader = "Authorization"; public static string GetJsonServiceGroupURL(string aosUriString, string serviceGroupName) { var serviceUriStringTemplate = "{0}/api/services/{1}"; var serviceUriString = string.Format(serviceUriStringTemplate, aosUriString.TrimEnd('/'), serviceGroupName); return serviceUriString; } public static string GetJsonServiceNameURL(string aosUriString, string serviceGroupName, string serviceName) { var serviceUriStringTemplate = "{0}/{1}"; var serviceUriString = string.Format(serviceUriStringTemplate, GetJsonServiceGroupURL(aosUriString, serviceGroupName), serviceName); return serviceUriString; } public static string GetJsonOperationURL(string aosUriString, string serviceGroupName, string serviceName, string functionName) { var serviceUriStringTemplate = "{0}/{1}"; var serviceUriString = string.Format(serviceUriStringTemplate, GetJsonServiceNameURL(aosUriString, serviceGroupName, serviceName), functionName); return serviceUriString; } } } 1. Описать класс-контракт, описанный на X++, т.к. в отличие от SOAP - здесь не передаются объекты, а значит для получения карточки клиента - нам потребуется повторное описание полей карточки. Это делается так: X++: public class Tutorial_CustServiceContract { public string parmCustAccount { get; set; } [JsonProperty("parmCustAccountName")] public string custAccountName { get; set; } public string parmCustGroupId { get; set; } } 2. Библиотека Newtonsoft.Json умеет сериализовать / десериализовать только классы, поэтому если нам надо передать в сервис обычное значение (например, код клиента), то чтобы вручную не формировать строку JSON - нужно создать классы, состоящие из одной этой переменной: X++: public class ParmCustAccount { [JsonProperty("_custAccount")] public string custAccount { get; set; } } public class ParmCustGroupId { [JsonProperty("_custGroupId")] public string custGroupId { get; set; } } В итоге у меня получилось 2 класса: JsonServices, который вызывает веб-сервис X++: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net; using System.IO; using Newtonsoft.Json; using AuthenticationUtility; namespace Tutorial_ConsumeServiceJSON { public class Tutorial_CustServiceContract { public string parmCustAccount { get; set; } [JsonProperty("parmCustAccountName")] public string custAccountName { get; set; } public string parmCustGroupId { get; set; } } public class ParmCustAccount { [JsonProperty("_custAccount")] public string custAccount { get; set; } } public class ParmCustGroupId { [JsonProperty("_custGroupId")] public string custGroupId { get; set; } } public class JsonServices { public const string serviceGroupName = "Tutorial_CustServiceGroup"; public const string serviceName = "Tutorial_CustService"; public const string getCustDetailName = "getCustDetail"; public const string getCustListOfGroupName = "getCustListOfGroup"; string aosUriString; string bearerKey; [JsonProperty("return")] public Tutorial_CustServiceContract[] serviceContracts { get; set; } [JsonProperty("return")] public Tutorial_CustServiceContract serviceContract { get; set; } public JsonServices() { aosUriString = ClientConfiguration.Default.ActiveDirectoryResource; bearerKey = OAuthHelper.GetAuthenticationHeader(true); } private HttpWebRequest CreateRequest(string _address) { HttpWebRequest webRequest; webRequest = (HttpWebRequest)HttpWebRequest.Create(_address); webRequest.Method = "POST"; // the request will be empty. webRequest.ContentLength = 0; webRequest.Headers.Set(JsonUtil.OAuthHeader, bearerKey); return webRequest; } private string ReadJsonResponse(HttpWebRequest _request) { string jsonString; using (HttpWebResponse webResponse = (HttpWebResponse)_request.GetResponse()) { using (Stream stream = webResponse.GetResponseStream()) { using (StreamReader reader = new StreamReader(stream)) { jsonString = reader.ReadToEnd(); } } } return jsonString; } public Tutorial_CustServiceContract getCustDetail(string _custAccount) { string operationURL = JsonUtil.GetJsonOperationURL(aosUriString, serviceGroupName, serviceName, getCustDetailName); ParmCustAccount parmCustAccount = new ParmCustAccount(); parmCustAccount.custAccount = _custAccount; string jsonCustAccount = JsonConvert.SerializeObject(parmCustAccount); HttpWebRequest webRequest; webRequest = CreateRequest(operationURL); webRequest.SendChunked = true; using (Stream stream = webRequest.GetRequestStream()) { using (StreamWriter writer = new StreamWriter(stream)) { writer.Write(jsonCustAccount); writer.Flush(); } } string jsonResponse = ReadJsonResponse(webRequest); serviceContract = JsonConvert.DeserializeObject<Tutorial_CustServiceContract>(jsonResponse); return serviceContract; } public Tutorial_CustServiceContract[] getCustListOfGroup(string _custGroupId) { string operationURL = JsonUtil.GetJsonOperationURL(aosUriString, serviceGroupName, serviceName, getCustListOfGroupName); ParmCustGroupId parmCustGroupId = new ParmCustGroupId(); parmCustGroupId.custGroupId = _custGroupId; string jsonCustGroupId = JsonConvert.SerializeObject(parmCustGroupId); HttpWebRequest webRequest; webRequest = CreateRequest(operationURL); webRequest.SendChunked = true; using (Stream stream = webRequest.GetRequestStream()) { using (StreamWriter writer = new StreamWriter(stream)) { writer.Write(jsonCustGroupId); writer.Flush(); } } string jsonResponse = ReadJsonResponse(webRequest); serviceContracts = JsonConvert.DeserializeObject<Tutorial_CustServiceContract[]>(jsonResponse); return serviceContracts; } } } X++: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net; using System.IO; using AuthenticationUtility; namespace Tutorial_ConsumeServiceJSON { class Program { static void Main(string[] args) { JsonServices jsonServices = new JsonServices(); Tutorial_CustServiceContract contract; Tutorial_CustServiceContract[] contracts; contract = jsonServices.getCustDetail("RUMF-000001"); Console.WriteLine(string.Format("{0} | {1} | {2}", contract.parmCustAccount, contract.custAccountName, contract.parmCustGroupId)); contracts = jsonServices.getCustListOfGroup("Орг"); foreach (Tutorial_CustServiceContract iterator in contracts) { Console.WriteLine(string.Format("{0} | {1} | {2}", iterator.parmCustAccount, iterator.custAccountName, iterator.parmCustGroupId)); } Console.ReadLine(); } } } Результат, как и в случае с SOAP должен быть таким же
__________________
Возможно сделать все. Вопрос времени Последний раз редактировалось sukhanchik; 09.12.2020 в 08:56. |
|
07.07.2020, 23:59 | #5 |
Administrator
|
Отдельно прикреплю проект. Все делалось в одном Solution.
Также хочу отметить, что домент @ivan-petrov.ru выдуманный, а созданное приложение в AAD уже удалено, поэтому в чистом виде данный код в режиме "скопировал и работает" работать не будет. Нужно будет подставить реальный домен, и реальные идентификаторы приложения и секрета. Ну и само собой данные должны быть в базе данных в соответствии с примером. 10.07.2020 Обновил архив и сообщения, т.к. как выяснилось - закралась ошибка. В строке, где инициализировалась переменная aosUriString - она бралась из файла ClientConfiguration, в котором переменная UriString была пустой. Исправил на ActiveDirectoryResource (т.е. переменная aosUriString теперь инициализируется из файла ClientConfiguration из переменной ActiveDirectoryResource). Исправление коснулось как SOAP, так и JSON. Поэтому архив перевыложил. EXE-шники от C# не перебилдивал - их все равно нужно будет перебилдивать после изменения файла ClientConfiguration на реальные значения идентификаторов
__________________
Возможно сделать все. Вопрос времени Последний раз редактировалось sukhanchik; 10.07.2020 в 20:28. |
|
|
За это сообщение автора поблагодарили: trud (20), Manner (1), AvrDen (1), Weez (3), Jorj (1), imir (2), Pandasama (1). |
|
|