27.12.2017, 22:02 | #1 |
Administrator
|
D365 FO Ссылка на конкретную запись. Параметры URL
Добрый день! В рамках исследования параметров URL, через которые можно задать те или иные команды системе были найдены следующие интересные моменты:
1. Встроенные команды. https://community.dynamics.com/ax/b/...url-parameters В том числе, наиболее интересные: prt=[partitionID] - указание раздела, в котором будет проводиться работа cmp=[legal entity] - указание компании, в которой будет проводиться работа lng=[LanguageId] - указание языка интерфейса. Очень удобно, когда хочется посмотреть как одна и та же надпись выглядит как по-русски, так и по-английски mi=[menuItem] - указание пункта меню, который требуется вызвать f=[formName] - указание названия формы, которую нужно открыть напрямую, не через пункт меню q=[queryString] - запрос, который передается на форму и позволяет отфильтровать данные формы 2. Запрос система может генерить сама (см https://axology.wordpress.com/2016/1...or-operations/). Для этого нужно в параметрах пользователя включить параметр "Автоматическое обновление параметров запроса" и воспользоваться расширенным фильтром на форме Запрос зашифрован, однако если его расшифровать - то там будет строка вида Параметр1=Значение1&Параметр2=Значение2, где параметры - это поля фильтра запроса (по сути - предложение WHERE в SQL-запросе). Однако D365 принимает только зашифрованную строку, так что смысла в дешифровке особой нет. Возможно, есть параметры, при которых шифрование отключается - но я их пока не видел (в АХ 2012 и ранее на портале такой параметр был) 3. Каждый запускаемый объект имеет право самостоятельно определять дополнительные параметры URL, которые он готов принять. Пример - см класс SysTableBrowser и параметр TableName: 4. Есть официальная статья Create and use deep links, в которой приводится пример, как сгенерить URL-адрес, открывающий заданный пункт меню с заданным фильтром (Query) X++: // gets the generator instance var generator = new Microsoft.Dynamics.AX.Framework.Utilities.UrlHelper.UrlGenerator(); var currentHost = new System.Uri(UrlUtility::getUrl()); generator.HostUrl = currentHost.GetLeftPart(System.UriPartial::Authority); generator.Company = curext(); generator.MenuItemName = <menu item name>; generator.Partition = getCurrentPartition(); // repeat this segment for each datasource to filter var requestQueryParameterCollection = generator.RequestQueryParameterCollection; requestQueryParameterCollection.AddRequestQueryParameter( <datasource name>, <field1>, <value1>, <field2>, <value2>, <field3>, <value3>, <field4>, <value4>, <field5>, <value5> ); System.Uri fullURI = generator.GenerateFullUrl(); // to get the encoded URI, use the following code fullURI.AbsoluteUri X++: public str generateFullURL() { str ret; FormRun formRun = element.args().caller(); FormDataSource formDS = formRun.dataSource(); if (formRun && formRun.isRootNavigable()) { System.Uri host = SessionContext::Get_Current().Get_RequestUrl(); UrlHelper.UrlGenerator generator = new UrlHelper.UrlGenerator(); generator.MenuItemName = formRun.args().menuItemName(); generator.MenuItemType = formRun.args().menuItemType(); generator.HostUrl = host.GetLeftPart(System.UriPartial::Path); generator.Company = curExt(); generator.EncryptRequestQuery = true; Microsoft.Dynamics.AX.Framework.Utilities.UrlHelper.RequestQueryParameterCollection requestQueryParameterCollection = generator.RequestQueryParameterCollection; DictTable dictTable = new DictTable(formDS.cursor().TableId); FieldId primaryKey = dictTable.primaryKeyField(); if (!primaryKey) { primaryKey = fieldnum(Common, RecId); } requestQueryParameterCollection.AddRequestQueryParameter(formDS.name(), fieldId2Name(dictTable.id(), primaryKey), strfmt("%1", formDS.cursor().(primaryKey))); ret = generator.GenerateFullUrl().AbsoluteUri; } return ret; } Здесь вызывается пункт меню FormRunGetLinkAction, который вызывает одноименную форму. Теперь есть 2 варианта, как вставить наш код в эту форму (без оверлеинга): А. Добавить Post-обработчик на метод run формы и в контрол Link записать наш текст. Это легкий способ, но он перезатирает стандартную функциональность Б. Добавить Post-обработчик на метод run формы и добавить в рантайме динамически новый контрол. Расширение (Extension) к самой форме создать не получится - установленные паттерны на ней не позволяют без ошибок компиляции добавить статический контрол. А менять паттерны Extension не разрешает. Пойдем вторым путем (вариант Б). Не забудем вставить проверку существования контрола, на случай, если кнопку "Получить ссылку" попробуют нажать при уже открытой форме X++: public void postRun() { this.showFullLink(); } public void showFullLink() { if (element.args() && element.args().caller() && element.args().callerType() == UtilElementType::Form) { FormStringControl ctrlFullLink = this.ctrlFullLink(); if (!ctrlFullLink) { ctrlFullLink = element.design().addControl(FormControlType::String, ctrlFullLinkName); } ctrlFullLink.allowEdit(false); ctrlFullLink.displayLengthMode(this.ctrlLink().displayLengthMode()); ctrlFullLink.displayLength(this.ctrlLink().displayLength()); ctrlFullLink.label("@SYS22569"); ctrlFullLink.text(this.generateFullURL()); } } X++: using Microsoft.Dynamics.AX.Framework.Utilities; using Microsoft.Dynamics.@Client.ServerForm.Contexts; class FormRunGetLinkActionHandler { FormRun element; private const str ctrlFullLinkName = "FullLink"; public FormRun formRun (FormRun _formRun = element) { element = _formRun; return element; } public static FormRunGetLinkActionHandler construct(FormRun _formRun) { FormRunGetLinkActionHandler handler = new FormRunGetLinkActionHandler(); handler.formRun(_formRun); return handler; } public str generateFullURL() { str ret; FormRun formRun = element.args().caller(); FormDataSource formDS = formRun.dataSource(); if (formRun && formRun.isRootNavigable()) { System.Uri host = SessionContext::Get_Current().Get_RequestUrl(); UrlHelper.UrlGenerator generator = new UrlHelper.UrlGenerator(); generator.MenuItemName = formRun.args().menuItemName(); generator.MenuItemType = formRun.args().menuItemType(); generator.HostUrl = host.GetLeftPart(System.UriPartial::Path); generator.Company = curExt(); generator.EncryptRequestQuery = true; Microsoft.Dynamics.AX.Framework.Utilities.UrlHelper.RequestQueryParameterCollection requestQueryParameterCollection = generator.RequestQueryParameterCollection; DictTable dictTable = new DictTable(formDS.cursor().TableId); FieldId primaryKey = dictTable.primaryKeyField(); if (!primaryKey) { primaryKey = fieldnum(Common, RecId); } requestQueryParameterCollection.AddRequestQueryParameter(formDS.name(), fieldId2Name(dictTable.id(), primaryKey), strfmt("%1", formDS.cursor().(primaryKey))); ret = generator.GenerateFullUrl().AbsoluteUri; } return ret; } public void postRun() { this.showFullLink(); } public void showFullLink() { if (element.args() && element.args().caller() && element.args().callerType() == UtilElementType::Form) { FormStringControl ctrlFullLink = this.ctrlFullLink(); if (!ctrlFullLink) { ctrlFullLink = element.design().addControl(FormControlType::String, ctrlFullLinkName); } ctrlFullLink.allowEdit(false); ctrlFullLink.displayLengthMode(this.ctrlLink().displayLengthMode()); ctrlFullLink.displayLength(this.ctrlLink().displayLength()); ctrlFullLink.label("@SYS22569"); ctrlFullLink.text(this.generateFullURL()); } } public FormStringControl ctrlLink() { return element.design().controlName(formControlStr(FormRunGetLinkAction, Link)); } public FormStringControl ctrlFullLink() { return element.design().controlName(ctrlFullLinkName); } [PostHandlerFor(formStr(FormRunGetLinkAction), formMethodStr(FormRunGetLinkAction, run))] public static void FormRunGetLinkAction_Post_run(XppPrePostArgs _args) { FormRunGetLinkActionHandler handler = FormRunGetLinkActionHandler::construct(_args.getThis()); handler.postRun(); } } По нижней ссылке можно открыть одну отфильтрованную запись. Ну и можно приложить модель (я все делал в отдельной модели) - файл axmodel (выгружался из PU10), выгруженный проект (axpp-файл) и решение (solution) в студии, чтобы пример было удобно открыть из студии SysGetFullShareLink-VSUH.axmodel.rar SysGetFullShareLink.axpp SysGetFullShareLink_Project.rar
__________________
Возможно сделать все. Вопрос времени Последний раз редактировалось sukhanchik; 28.12.2017 в 13:49. |
|
|
За это сообщение автора поблагодарили: mazzy (10), S.Kuskov (2), belugin (10), Logger (10), raz (10), Jorj (1), Ivanhoe (10), trud (7), gl00mie (10). |
28.12.2017, 09:48 | #2 |
Участник
|
Круто. А есть ли защита от дурака и от умного? Явное отключение параметров, защита от перебора и т.п.? Или только права доступа?
__________________
Ivanhoe as is.. |
|
28.12.2017, 11:31 | #3 |
Administrator
|
В статье Create and use deep links написано:
Цитата:
Security
Site access Access to the domain/client is controlled through the existing login and SSL mechanism. Form access Access to the form is controlled through the specified Menu Item, and the accompanying Menu Item security system. If a user navigates using a URL which contains a Menu Item that the user does not have access to, then the Menu Item security will prevent the form from opening. The user will receive message which says that they do not have the necessary permissions to open the form. Data access Access to data is controlled through the existing form-level queries. When a form is opened with a generated URL, the form will run its existing form-level queries, which restrict the user's access to data. The data context that is specified in the generated URL is consumed after these form-level queries are applied, and results only in further filtering of the data displayed to the user. In short, a generated URL can, at most, open a form and display all of the data that a form would display to the user based on the form-level queries. A generated URL cannot grant a user access to data that is otherwise inaccessible on the form when not using the generated URL. Доступ к сайту обеспечивается протоколом HTTPS Доступ к форме обеспечивается правами доступа к пункту меню, по которому генерится ссылка Доступ к данным обеспечивается с помощью запросов на уровне формы. Т.е. пользователь не увидит данные, которые не может увидеть с помощью конкретной формы. Ну т.е. по сути фильтр накладывается на queryRun при уже существующем query
__________________
Возможно сделать все. Вопрос времени |
|
28.12.2017, 11:46 | #4 |
Участник
|
Цитата:
Цитата:
Сообщение от sukhanchik
Соответственно, на базе этого кода можно написать свой, который будет фильтровать по RecId и т.о. отображать текущие записи. Тут правда есть нюанс - в стародавние времена было модно сложные запросы вида %1.%2 == %3.%4 || %5.%6 == %7.%8 делать как Range по полю RecId, как по наиболее редко используемому полю в запросах. В этом случае фильтрация по RecID может работать некорректно.
|
|
28.12.2017, 12:07 | #5 |
Administrator
|
Будет. Формирование запроса в моем случае производится до открытия формы. А есть некоторые формы (типа smmActivities), которые уже при открытии накладывают фильтр по RecId, т.е. уже после исполнения моего кода.
Учитывая, что это только один случай, то поэтому и хочется собрать статистику по прочим формам, т.к. естественно я не мог протестировать это на всех формах. Пока по результатам тестирования выявлена только одно неудобство - кэширование. Т.е. при переходе между записями на форме - сама форма кэшируется (с т.з. программного кода - не закрывается) и в результате ссылка на вторую запись не формируется (не меняется после формирования по первой записи). Я пока думаю, как это можно побороть
__________________
Возможно сделать все. Вопрос времени Последний раз редактировалось sukhanchik; 28.12.2017 в 12:15. |
|
28.12.2017, 13:37 | #6 |
Administrator
|
Ошибка нашлась - в моем примере range на первичный ключ просто добавлялся, а не создавался заново. В результате повторного вызова генерации ссылки - система добавляла range, который естественно срабатывал по ИЛИ.
Код в исходном сообщении исправил, модель и проект перевыложил. Теперь ссылка генерится для каждой записи - своя. Кстати, посмотреть результаты исполнения запроса можно после открытия формы, если открыть расширенный фильтр:
__________________
Возможно сделать все. Вопрос времени |
|
28.12.2017, 13:49 | #7 |
Участник
|
Если создавать заново, то открывается широкое поле для просмотра запрещенного контента через отрицание.
например, фильтр !"" показывает ВСЕ непустое |
|
28.12.2017, 14:37 | #8 |
Administrator
|
Цитата:
Это я уже через класс RequestQueryParameterCollection создаю новый Range и накладываю его на первичный ключ. Ошибка была в том, что при повторном вызов старый Range не удалялся, а новый создавался. В результате получалось так, что после перехода по ссылке - на форму накладывались 2 Range на одно поле, и они естественно работали по ИЛИ. Сейчас накладывается только 1 Range на первичный ключ В общем - цель получения ссылки на конкретную запись - не ограничение прав пользователя, а отправки ему ссылки на конкретную запись со словами - "посмотри сюда".
__________________
Возможно сделать все. Вопрос времени Последний раз редактировалось sukhanchik; 28.12.2017 в 14:45. |
|
|
За это сообщение автора поблагодарили: mazzy (2). |
29.12.2017, 23:50 | #9 |
Участник
|
А что с этим постом не так thwidmer: Deep links available in every form ?
И зачем "if (formRun" ? Он или есть или мы ловим null reference exception до if X++: FormDataSource formDS = formRun.dataSource();
if (formRun && formRun.isRootNavigable()) |
|
30.12.2017, 00:19 | #10 |
Участник
|
И чтобы поддержать дух исследования хочется отметить что ссылки можно генерить и без аксапты, к примеру, из консольного приложения:
X++: using Microsoft.Dynamics.AX.Framework.Utilities.UrlHelper; using System; namespace URLGeneratorTest { class Program { static void Main(string[] args) { UrlGenerator generator = new UrlGenerator(); generator.MenuItemName = "VendTable"; generator.MenuItemType = UrlGenerator.MenuItemTypes.Display; generator.HostUrl = @"https://yourvmname.cloudax.dynamics.com/"; generator.Company = "usmf"; var requestQueryParameterCollection = generator.RequestQueryParameterCollection; requestQueryParameterCollection.UpdateOrAddEntry("VendTable", "AccountNum", "1002"); Console.WriteLine(generator.GenerateFullUrl().AbsoluteUri); } } } |
|
|
За это сообщение автора поблагодарили: sukhanchik (2), trud (7). |
30.12.2017, 01:20 | #11 |
Administrator
|
Цитата:
Сообщение от skuull
А что с этим постом не так thwidmer: Deep links available in every form ?
В данном примере делается расширение класса и мы получим изменим исходную ссылку. Т.е. это будет "вариант А". Но с другой стороны - концептуально (на мой взгляд) сделать именно Extension как в том примере - более правильно. Мой пример хорош как вариант кода "с одним объектом". Ну и мне всегда нравятся готовые примеры, которые можно скачать, влить к себе без лишних телодвижений и они сразу заработают. Я не ломал стандартный функционал только по одной причине - если ссылка не будет работать (из-за внутренней логики какой-нибудь формы), то останется возможность использовать обычную (штатную) ссылку. Если бы я мог гарантировать работоспособность своего примера в 100% случаях всех форм, то можно было бы и заменить штатный функционал на измененный. Ну и вообще моя позиция такова - что очень не хватает материалов на русском языке, даже если есть альтернатива на английском (не люблю буржуйский язык). Я не пользовался той статьей и она мне при поиске не попалась, поэтому привожу примеры на русском языке, даже если есть альтернативы на английском. Если источник примеров был на английском языке - то делаю ссылку. Если пример был написан самостоятельно - то ссылку не делаю. Код был взят с исходной формы FormRunGetLinkAction, плюс добавлено обновление датасорса. Замечание корректное, но ... null reference exception никогда не будет словлено, т.к. панель кнопок всегда привязана к исходной форме. Т.е. здесь просто лишняя проверка на formRun, которая в целом никому не мешает.
__________________
Возможно сделать все. Вопрос времени Последний раз редактировалось sukhanchik; 30.12.2017 в 01:33. |
|
30.12.2017, 01:27 | #12 |
Administrator
|
Цитата:
В целом, из C# можно вызывать любые классы X++, только ... вот граблей там хватает . Чтобы вот этот вот using написать - нужно Reference добавить, но не через GAC, а через dll-ку. И перебилдивать проект после обновления dll-ки
__________________
Возможно сделать все. Вопрос времени |
|
30.12.2017, 02:16 | #13 |
Участник
|
Цитата:
Выкладывая код на всеобщее обозрение надо быть готовым к тому что вам скажут про construct с параметрами который не best practice и про прочие мелочи |
|
30.12.2017, 02:40 | #14 |
Участник
|
Цитата:
Сообщение от sukhanchik
Интересный пример, только ... в готовом виде невостребован. Т.е. этот код хорошо вставлять в другие приложения, причем четко понимать откуда брать параметры, какую форму открывать и по каким значениям фильтровать.
В целом, из C# можно вызывать любые классы X++, только ... вот граблей там хватает . Чтобы вот этот вот using написать - нужно Reference добавить, но не через GAC, а через dll-ку. И перебилдивать проект после обновления dll-ки Последний раз редактировалось skuull; 30.12.2017 в 02:54. |
|
30.12.2017, 11:39 | #15 |
Administrator
|
Цитата:
Сообщение от skuull
Ну как никогда... Вы написали класс который принимает FormRun т.е. кто угодно может передать туда любой FormRun у которого может и не быть caller и напрямую вызвать generateFullURL, т.к. он публичный. Так же вы его назвали "полноценный". Это дает мне основание думать, что это не какой-то там примерчик, а целый класс, бери и используй
Теоретически возможно все и от дураков никогда не защитишься. Именно поэтому я и не стремлюсь это делать. Кому потребуется - тот навесит на пример дополнительные проверки и средства защиты. Цитата:
Выкладывая же код на данный общедоступный ресурс я исхожу из того, что пока больше никто аналогичных действий на этом ресурсе не делает, поэтому оставляю за собой право допускать несущественные (т.е. не ломающие работоспособность примера) ошибки и игнорировать сообщения про best pratice от тех, кто аналогичные примеры не выкладывает
__________________
Возможно сделать все. Вопрос времени Последний раз редактировалось sukhanchik; 30.12.2017 в 11:46. |
|
|
За это сообщение автора поблагодарили: mazzy (2). |
30.12.2017, 11:44 | #16 |
Administrator
|
Цитата:
Но в целом - это все понятно и в сторонних системах конечно будут использовать код для вызова логики D365 FO
__________________
Возможно сделать все. Вопрос времени |
|