AXForum  
Вернуться   AXForum > Microsoft Dynamics AX > DAX: Программирование
All
Забыли пароль?
Зарегистрироваться Правила Справка Пользователи Сообщения за день Поиск Все разделы прочитаны

 
 
Опции темы Поиск в этой теме Опции просмотра
Старый 20.01.2021, 16:44   #41  
trud is offline
trud
Участник
Лучший по профессии 2017
 
1,038 / 1629 (57) ++++++++
Регистрация: 07.06.2003
Записей в блоге: 1
Спасибо что поделились. Еще как вариант можно наверное сделать отдельную табличку(Статус, Результирующий файл), запускать эту операцию в пакете и приатачивать файл к этой табличке по завершению. Пользователь соответсвенно будет скачивать файл когда он сформируется и может вообще закрыть браузер
Старый 20.01.2021, 17:30   #42  
kair84 is offline
kair84
Участник
 
47 / 58 (2) ++++
Регистрация: 15.04.2010
Адрес: Belarus
Цитата:
Сообщение от trud Посмотреть сообщение
Спасибо что поделились. Еще как вариант можно наверное сделать отдельную табличку(Статус, Результирующий файл), запускать эту операцию в пакете и приатачивать файл к этой табличке по завершению. Пользователь соответсвенно будет скачивать файл когда он сформируется и может вообще закрыть браузер
Конечно, можно развивать идею на свой вкус и цвет.

Для моих целей этого вполне достаточно, я всего лишь хотел восстановить возможность взаимодействия с пользователем после выполнения операции в отдельной сессии.

Доработал пример для корректной работы в пакетном режиме
X++:
// This is a framework class. Customizing this class may cause problems with future upgrades to the software.
class Test_RunbaseBatch extends RunBaseBatch
{
    // Packed variables
    str             csvFileContent;
    Email           email2Send;

    #define.CurrentVersion(1)
    #localmacro.CurrentList
        csvFileContent,
        email2Send
    #endmacro

    public boolean getFromDialog()
    {
        boolean ret;

        ret = super();

        email2Send = SysUserInfo::getUserEmail(curUserId());

        return ret;
    }

    public container pack()
    {
        return [#CurrentVersion,#CurrentList];
    }

    public void run()
    {
        info("run");

        if (! this.validate())
            throw error("");

        commaStreamIo iO = commaStreamIo::constructForWrite();

        container header = ["Num"];
        iO.writeExp(header);

        int i;
        for (i=1; i<=660; i++)
        {
            iO.write(i);

            //sleep(1000); //Over 10 min.
            sleep(100);
        }

        System.IO.Stream stream = iO.getStream();
        stream.Position = 0;
        System.IO.StreamReader reader = new System.IO.StreamReader(stream);
        csvFileContent = reader.ReadToEnd();

        if (this.isInBatch())
        {
            this.runAfterOperation();
        }

    }

    public void runAfterOperation()
    {
        info("runAfterOperation");

        Filename filename = "file.csv";
        System.Byte[] byteArray =  System.Text.Encoding::Unicode.GetBytes(csvFileContent);
        System.IO.MemoryStream stream = new System.IO.MemoryStream(byteArray);
        stream.Position = 0;

        if (this.isInBatch())
        {
            if (SysEmailDistributor::validateEmail(email2Send))
            {
                SysMailerMessageBuilder messageBuilder = new SysMailerMessageBuilder();
                messageBuilder.setFrom(email2Send,"@SYS115063")
                              .addTo(email2Send)
                              .setPriority(1)
                              .setSubject(this.caption())
                              .setBody(this.caption())
                              .addAttachment(stream, fileName);
                SysMailerFactory::sendNonInteractive(messageBuilder.getMessage());
                info(strFmt("CSV file %1 Sent to user on e-mail %2",filename,email2Send));

            }
        }
        else
        {
            File::SendFileToUser(stream, fileName);
            info(strFmt("CSV file %1 Sent to user",filename));
        }

    
    }

    public boolean runsImpersonated()
    {
        return true;
    }

    public boolean unpack(container packedClass)
    {
        Version version = RunBase::getVersion(packedClass);
    ;
        switch (version)
        {
            case #CurrentVersion:
                [version,#CurrentList] = packedClass;
                break;
            default:
                return false;
        }

        return true;
    }

    public boolean validate(Object _calledFrom = null)
    {
        if (false)
            return checkFailed("");

        return true;
    }

    static ClassDescription description()
    {
        return "Test RunBase";
    }

    static Test_RunbaseBatch construct()
    {
        return new Test_RunbaseBatch();
    }

    static void main(Args args)
    {
        Test_RunbaseBatch    runBase;

        runBase = Test_RunbaseBatch::construct();

        if (runBase.prompt())
        {
            runBase.runOperation();

            if (!runBase.batchInfo().parmBatchExecute())
            {
                runBase.runAfterOperation();
            }
        }

    }

    protected boolean canRunInNewSession()
    {
        return true;
    }

}
Старый 22.01.2021, 15:33   #43  
kair84 is offline
kair84
Участник
 
47 / 58 (2) ++++
Регистрация: 15.04.2010
Адрес: Belarus
Теперь по теме:
Вот как процесс обработчик может информировать пользователя о прогрессе
Нажмите на изображение для увеличения
Название: SysOp_Progress.JPG
Просмотров: 26
Размер:	57.4 Кб
ID:	13030
Старый 22.01.2021, 15:46   #44  
trud is offline
trud
Участник
Лучший по профессии 2017
 
1,038 / 1629 (57) ++++++++
Регистрация: 07.06.2003
Записей в блоге: 1
Круто выглядит. А как вы этого добились? я что-то думал что прогресс бар убрали
Старый 22.01.2021, 16:54   #45  
kair84 is offline
kair84
Участник
 
47 / 58 (2) ++++
Регистрация: 15.04.2010
Адрес: Belarus
Работает только для SysOperation, в Asynchronous и ReliableAsynchronous режимах.
Важно параметризировать parmShowProgressForm(true) в контроллере, и в сервисе получить RunbaseProgress.


Вот код:

Controller
X++:
class Test_SysOperationController extends SysOperationServiceController
{
    protected void loadFromSysLastValue()
    {
        if (!dataContractsInitialized)
        {
            // This is a bug in the SysOperationController class
            // never load from syslastvalue table when executing in batch
            // it is never a valid scenario
            if (!this.isInBatch())
            {
                super();
            }

            dataContractsInitialized = true;
        }
    }

    public void new()
    {
        super();

        // defaulting parameters common to all scenarios

        // If using reliable async mechanism do not wait for the batch to
        // complete. This is better done at the application level since
        // the batch completion state transition is not predictable
        //this.parmRegisterCallbackForReliableAsyncCall(false);

        // default for controllers in these samples is synchronous execution
        // batch execution will be explicity specified. The default for
        // SysOperationServiceController is ReliableAsynchronous execution

        //this.parmExecutionMode(SysOperationExecutionMode::ReliableAsynchronous);
        this.parmExecutionMode(SysOperationExecutionMode::Asynchronous);
        
        //this.parmExecutionMode(SysOperationExecutionMode::Synchronous); // ProgressBar doesn't work.

        this.parmShowProgressForm(true);
        
        this.parmClassName(classStr(Test_SysOperationService));
        this.parmMethodName(methodStr(Test_SysOperationService, runOperation));
        //this.parmMethodName(methodStr(Test_SysOperationService, runOperationResult)); // Doesn't work ???
        
    }

    public ClassDescription caption()
    {
        return 'Test SysOperation !';
    }

    protected boolean canRunInNewSession()
    {
        return true;
    }

    public void asyncCallbackVoid(AsyncTaskResult _asyncResult)
    {
        info("Async Callback Void");
        
        super(_asyncResult);

        /*
        SysOperationDataContractInfo contractInfo = this.getDataContractInfoObjects().lookup("dataContract");        
        Test_SysOperationDataContract dataContract = contractInfo.dataContractObject();
        */

        //Box::info("Async Callback  Void Box");
        //this.sendFile("Async Callback Void");

    }

    public void asyncCallbackReturnValue(AsyncTaskResult _asyncResult, anytype _returnValue)
    {
        // Doesn't work ???
        info("Async Callback Result");

        //super(_asyncResult,_returnValue);
        
        //Box::info("Async Callback  Result Box");

    }

    public void sendFile(str _fileContent)
    {
        Filename filename = "file.csv";
        System.Byte[] byteArray =  System.Text.Encoding::Unicode.GetBytes(_fileContent);
        System.IO.MemoryStream stream = new System.IO.MemoryStream(byteArray);
        stream.Position = 0;

        File::SendFileToUser(stream, fileName);
        info(strFmt("File %1 Sent to user",filename));
    
    }

    public static void main(Args args)
    {
        Test_SysOperationController operation;

        operation = new Test_SysOperationController();
        operation.startOperation();
    }

}
DataContract
X++:
[DataContractAttribute,
SysOperationContractProcessingAttribute(classStr(Test_SysOperationUIBuilder))]
    class Test_SysOperationDataContract extends SysOperationDataContractBase
{
    int duration;

    [DataMemberAttribute,
        SysOperationLabelAttribute('Duration (sec.)'),
        SysOperationHelpTextAttribute('Type some number >= 0'),
        SysOperationDisplayOrderAttribute('1')]
    public int parmDuration(int _duration = duration)
    {
        duration = _duration;

        return duration;
    }

}
UIBuilder
X++:
class Test_SysOperationUIBuilder extends SysOperationAutomaticUIBuilder
{
    DialogField durationField;

    public void postBuild()
    {
        super();

        // get references to dialog controls after creation
        durationField = this.bindInfo().getDialogField(this.dataContractObject(), methodStr(Test_SysOperationDataContract, parmDuration));
        
    }

    public void postRun()
    {
        super();

        // register overrides for form control events
        durationField.registerOverrideMethod(methodstr(FormIntControl, validate), methodstr(Test_SysOperationUIBuilder, durationFieldValidate), this);
    }

    public boolean durationFieldValidate(FormIntControl _control)
    {
        if (_control.value() < 0)
        {
            error('Please type a number >= 0');
            return false;
        }
        return true;
    }

}
Service
X++:
class Test_SysOperationService extends SysOperationServiceBase
{
    public void runOperation(Test_SysOperationDataContract dataContract)
    {
        RunbaseProgress                 progress = this.getProgressController(dataContract);
        int total = dataContract.parmDuration();

        progress.setTotal(total);

        int i;
        for (i = 1; i <= total; i++ )
        {
            progress.incCount();
            progress.setText(strFmt("%1 / %2",i,total));
            progress.update(true);
            sleep(1000);
        }

        info('Done!');

    }

    public container runOperationResult(Test_SysOperationDataContract dataContract)
    {
        this.runOperation(dataContract);

        return [dataContract.parmDuration()];

    }

}


Может кто то подскажет, почему не работает asyncCallbackReturnValue(..) для parmMethodName(methodStr(Test_SysOperationService, runOperationResult)) ?
Выбрасывается ошибка что нет такого метода но вот же он... ???
За это сообщение автора поблагодарили: Logger (5).
Старый 26.01.2021, 18:06   #46  
kair84 is offline
kair84
Участник
 
47 / 58 (2) ++++
Регистрация: 15.04.2010
Адрес: Belarus
C asyncCallbackReturnValue(..) разобрался, это баг в стандартном методе SysOperationServiceController.asyncCallbackReturnValue(..). Колбэк возвращаяет одну переменную, значит второй параметр нужно сделать необязательным.

Вот так все хорошо, и _returnValue имеет правильное значение, ровно то что вернул метод сервиса.
X++:
...
    public void asyncCallbackReturnValue(AsyncTaskResult _asyncResult, anytype _returnValue  = _asyncResult.getResult())
    {
        info(strFmt("Async Callback Result: %1",con2Str(_returnValue)));

        //super(_asyncResult,_returnValue);
        
        //Box::info("Async Callback  Result Box");

    }
...

Последний раз редактировалось kair84; 26.01.2021 в 18:14.
За это сообщение автора поблагодарили: madm (1).
Старый 27.01.2021, 12:52   #47  
kair84 is offline
kair84
Участник
 
47 / 58 (2) ++++
Регистрация: 15.04.2010
Адрес: Belarus
Вижу что тема Вам не сильно интересна, хотя казалось что даже очень.
Чтоб закрыть её, вот как можно сделать прогресс для обычного RunBase :

X++:
class Test_RunBaseBatch extends RunBaseBatch
{
    DialogField     dfDuration;

    // Packed variables
    int             duration;
    str             csvFileContent;
    Email           email2Send;
    guid            callId;


    #define.CurrentVersion(1)
    #localmacro.CurrentList
        callId,
        duration,
        csvFileContent,
        email2Send
    #endmacro

    SysOperationProgressWait progressWait;
    //SysProgress sysProgress;

    public object dialog()
    {
        DialogRunbase dialog;

        dialog = super();

        dfDuration = dialog.addFieldValue(identifierStr(Integer),duration,"Duration (Sec.)");


        return dialog;
    }

    public boolean getFromDialog()
    {
        boolean ret;

        ret = super();

        duration = dfDuration.value();

        email2Send = SysUserInfo::getUserEmail(curUserId());

        return ret;
    }

    public container pack()
    {
        return [#CurrentVersion,#CurrentList];
    }

    public void runAsync()
    {
        callId = newGuid();
        progressWait = SysOperationProgressWait::construct();
        progressWait.parmCallId(callId);
        progressWait.parmCaption(this.caption());
        progressWait.parmBatchTaskId(0);


        xGlobal::runAsyncWithObjectCallback(
            classNum(Test_RunBaseBatch),
            staticMethodStr(Test_RunBaseBatch, doRunAsync),
            this.pack(),
            this, methodStr(Test_RunBaseBatch, runAsyncCallback));
        
        Message::Add(MessageSeverity::Informational ,"Start Operation Async");

        progressWait.beginWaiting();
    }

    private void runAsyncCallback(AsyncTaskResult _result)
    {
        progressWait.endWaiting();

        Message::Add(MessageSeverity::Informational ,"End Operation Async");

        container parms = _result.getResult();
        this.unpack(parms);
        this.runAfterOperation();

    }

    private static container doRunAsync(container _parms, System.Threading.CancellationToken cancellationToken)
    {
        Test_RunBaseBatch runbase = new Test_RunBaseBatch();
        runbase.unpack(_parms);
        runbase.run();
        return runbase.pack();
    }

    public void run()
    {
        Message::Add(MessageSeverity::Informational ,"run");

        if (! this.validate())
        throw error("");

        SysOperationProgressServer progressBar = new SysOperationProgressServer(1,false,callId);
        progressBar.setCaption("SysOperationProgressServer !!!");
        progressBar.setTotal(duration);



        commaStreamIo iO = commaStreamIo::constructForWrite();

        container header = ["Num"];
        iO.writeExp(header);

        int i;
        for (i=1; i<=duration; i++)
        {
            iO.write(i);

            progressBar.incCount();
            progressBar.setText(strFmt("%1 / %2",i,duration));

            sleep(1000);
        }

        
        System.IO.Stream stream = iO.getStream();
        stream.Position = 0;
        System.IO.StreamReader reader = new System.IO.StreamReader(stream);
        csvFileContent = reader.ReadToEnd();

        if (this.isInBatch())
        {
            this.runAfterOperation();
        }

        Message::Add(MessageSeverity::Informational ,"Done!");

    }

    public void runAfterOperation()
    {
        info("runAfterOperation");

        Filename filename = "file.csv";
        System.Byte[] byteArray =  System.Text.Encoding::Unicode.GetBytes(csvFileContent);
        System.IO.MemoryStream stream = new System.IO.MemoryStream(byteArray);
        stream.Position = 0;

        if (this.isInBatch())
        {
            if (SysEmailDistributor::validateEmail(email2Send))
            {
                SysMailerMessageBuilder messageBuilder = new SysMailerMessageBuilder();
                messageBuilder.setFrom(email2Send,"@SYS115063")
                            .addTo(email2Send)
                            .setPriority(1)
                            .setSubject(this.caption())
                            .setBody(this.caption())
                            .addAttachment(stream, fileName);
                SysMailerFactory::sendNonInteractive(messageBuilder.getMessage());
                info(strFmt("CSV file %1 Sent to user on e-mail %2",filename,email2Send));

            }
        }
        else
        {
            File::SendFileToUser(stream, fileName);
            info(strFmt("CSV file %1 Sent to user",filename));
        }

    
    }

    public boolean unpack(container packedClass)
    {
        Version version = RunBase::getVersion(packedClass);
        ;
        switch (version)
        {
            case #CurrentVersion:
                [version,#CurrentList] = packedClass;
                break;
            default:
                return false;
        }

        return true;
    }

    public boolean validate(Object _calledFrom = null)
    {
        if (false)
        return checkFailed("");

        return true;
    }

    static ClassDescription description()
    {
        return "Test RunBase Descr.";
    }

    static void main(Args args)
    {
        Test_RunBaseBatch    runBase;

        runBase = new Test_RunBaseBatch();
        runBase.init();

        if (runBase.prompt())
        {
            runBase.runAsync();
        }

    }

    protected boolean canRunInNewSession()
    {
        return false;
    }

}

Очевидно, что для того, чтобы во время выполнения кода на сервере была возможность взаимодействовать с пользователем, необходимо выполнять операцию асинхронно, а для того, чтобы связать серверный процесс с интерфейсом стали использовать таблицу SysProgress, в нее пишется вся необходимая информация о прогрессе (один или несколько прогресс-баров, заголовок, текст, и т.д) и обновляется в время выполнения.

Надеюсь проделанная мной работа не напрасна и пригодится кому то еще кроме меня. Возможно даже удостоится переноса в Блог.
За это сообщение автора поблагодарили: trud (10).
Старый 27.01.2021, 13:22   #48  
trud is offline
trud
Участник
Лучший по профессии 2017
 
1,038 / 1629 (57) ++++++++
Регистрация: 07.06.2003
Записей в блоге: 1
Тема полезная, спасибо что поделились
А зачем вызов "if (this.isInBatch()) " в методе run?
еще вопрос - а если вернуть в canRunInNewSession() = true, не получится ли упросить код?
Старый 27.01.2021, 14:22   #49  
kair84 is offline
kair84
Участник
 
47 / 58 (2) ++++
Регистрация: 15.04.2010
Адрес: Belarus
Цитата:
Сообщение от trud Посмотреть сообщение
...
А зачем вызов "if (this.isInBatch()) " в методе run?
...
Из батча выполнится только run(), и тогда из него нужно позвать runAfterOperation(), а для обычного запуска ( без батча) run() запустится асинхронно, а runAfterOperation() уже после, в синхронном режиме, для того чтобы проинтерактировать с юзером например. Это опционально можно и не делать вовсе.


Цитата:
Сообщение от trud Посмотреть сообщение
...
еще вопрос - а если вернуть в canRunInNewSession() = true, не получится ли упросить код?
Тогда в Main() нужно вызвать runOperation() а он имеет атрибут final, его нельзя перекрыть для запуска прогрессбара в виде SysOperationProgressWait перед стартом асинхронной операции. Хотя конечно можно выкрутиться и запустить его в prompt() после супера. И еще нужно не забыть где то его потушить.

Варианты всегда есть, ну или почти всегда. Когда уже становится ясно как именно работает этот прогресбар в 7, то и варианты могут различные найтись. Может быть кто то покопается еще и найдет способ оживить старый прогресбар.

Последний раз редактировалось kair84; 27.01.2021 в 14:27.
Теги
#страшнодалекиониотнарода

 

Похожие темы
Тема Автор Раздел Ответов Посл. сообщение
mfp: X++ in AX7: String truncation Blog bot DAX Blogs 6 29.05.2020 18:24
mfp: What is new in X++ in AX7? Blog bot DAX Blogs 2 10.02.2016 00:29
Пример использования RunBuf Mechanizm DAX: Программирование 11 02.03.2004 13:25
Пример использования класса RunBase* Andronov DAX: Программирование 3 17.09.2003 13:12
HB_Tutorial_setTmpData - пример использования метода setTmpData vitk DAX: База знаний и проекты 0 10.12.2001 15:26
Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход

Рейтинг@Mail.ru
Часовой пояс GMT +3, время: 17:49.
Powered by vBulletin® v3.8.5. Перевод: zCarot
Контактная информация, Реклама.