English version

При эксплуатации Системы существует ряд задач/функций как, импорт/экспорт данных или пакетное формирование отчетов, которые необходимо выполнять в автоматическом, пакетном режиме, возможно, по расписанию. Взаимодействие с пользователем в таких случаях минимально, или отсутствует вовсе.

Использовать для этих целей тяжелое, полнофункциональное Win или Web приложение с его мощным графическим интерфейсом, которое при инициализации подгружает десятки различных модулей может быть нецелесообразно или даже невозможно. Многие из загружаемых модулей могут быть вовсе не нужными для решения конкретной задачи.

Для подобного класса задач больше бы подошло легковесное консольное приложение, к которому подключен только требуемый для решения данной задачи набор модулей. Необходимые для решения конкретной задачи параметры могут передаваться в командной строке.

В настоящий момент нет возможности создать легковесную версию XAF приложения, которое позволило бы реализовать простой механизм выполнения требуемых функций XAF модулей, с возможностью запуска в консольном режиме.

Для решения описанных проблем необходимо:

  1. Реализовать возможность создания легковесных консольных приложений на платформе XAFARI (поддерживающих всю необходимую инфраструктуру Xafari приложения, включая доступность модели, базы данных). Причем таким приложениям не требуется графический интерфейс пользователя, что, в свою очередь, позволило бы не загружать и не инициализировать компоненты GUI.
  2. Реализовать инфраструктуру, позволяющую унифицированным образом описать функции (команды) произвольного XAF модуля;
  3. Создать консольную утилиту, позволяющую выполнять команды, предоставленные произвольными, определяемыми пользователем, модулями XAF;
  4. В составе утилиты реализовать механизмы гибкой настройки подсистемы аутентификации.

Описание проектных решений

Консольные приложения на платформе Xafari

Используя класс XafApplication в качестве базового класса, создан класс Xafari.WouiApplication (without user interface). В нем реализован абстрактный метод базового класса CreateLayoutManagerCore. Также реализованы методы, позволяющие выполнить аутентификацию (Logon, Logoff).

Класс Xafari.ConsoleApplication на текущем этапе не содержит никакой реализации и является чисто декларативным.
Этот класс и используется разработчиком при разработке консольного приложения, по аналогии с классом WinApplication: в приложении реализуется его наследник, после чего создается и инициализируется экземпляр наследника, например, в методе Main.

Декларирование команд, доступных для выполнения модулем.

Разработчики реализуют команды, заключая их в состав модуля XAF. Чтобы использовать в модуле инфраструктуру команд разработчик должен подключить к модулю сборку Xafari, пространство имен Xafari.Commands, который содержит необходимые классы и интерфейсы. На рисунке ниже приведена диаграмма классов, входящих в сборку:

Рисунок. Диаграмма классов Xafari.Commands.

Чтобы команды из модуля стали «видны» утилите запуска команд, класс командного модуля (наследник ModuleBase) должен реализовать интерфейс ICommandEnumerator. Интерфейс описывает единственное перечисляемое поле – Commands. Он может быть реализован на основе блока итератора, как на листинге 1.

Листинг 1. Реализация свойства ICommandEnumerator.Commands.

Каждая команда описывается отдельным классом. Этот класс должен реализовать интерфейс ICommand или ICommandExtIO. Свойство Description должно предоставлять описание команды, которое увидит пользователь при вызове справки по команде. Свойство Name должно предоставлять наименование команды. Наименование должно быть кратким, не содержать пробелов. При таком соглашении его становится проще использовать при указании команды, например, как параметра командной строки. Свойство ParametersDescription должно предоставлять коллекцию элементов типа CommandParameterDescription – описание параметров команды. Свойство Out с типом TextWriter служит для вывода текстовых данных кодом реализации команды в ходе ее выполнения. Это свойство инициализируется запускающим команду методом и разработчик команды не должен беспокоиться о его инициализации. Необходимо только учитывать, что при выполнении команды это свойство может оказаться не инициализированным (вызывающий метод не пожелал получать вывод команды).

И, наконец, код, непосредственно выполняющий полезную работу, т.е. осуществляющий исполнение команды должен быть реализован в методе Execute. Метод получает коллекцию элементов ключ-значение, представляющую параметры команды, а также ссылку на экземпляр XAF приложения, вызвавшего команду на исполнение.

Интерфейс ICommandExtIO расширяет ICommand, вводя два новых свойства - Error и In. Свойство Error, имеющее тип TextWriter, предназначено для вывода сообщений об ошибках, возникающих в ходе выполнения команды. Свойство In с типом TextReader может, при необходимости, использоваться для чтения данных, необходимых команде. Например, в консольной утилите запуска команд к этому свойству привязывается стандартный поток ввода Console.In. При этом реализация команды может читать данные из перенаправленного потока ввода командной строки, например, такого:

 C:\Utils\RunCmd.exe demo < somedata.dat 

Для упрощения задачи разработчику команд реализованы абстрактные классы CommandBase и CommandExtIOBase. Они содержат код, который может быть использован при реализации многих команд. Введен виртуальный метод ValidateParameters, выполняющих базовую проверку корректности параметров. В случае обнаружения некорректного параметра генерируется исключение типа ArgumentException. Метод считает все параметры необязательными. При необходимости разработчик может дополнить проверку параметров, перекрыв данный метод. При реализации дополнительных проверки параметров разработчиком, в случае обнаружения некорректных параметров необходимо также генерировать исключение типа ArgumentException или его наследника.

Свойства Out, Error, In реализованы таким образом, чтобы в коде команды разработчику не нужно было беспокоиться о том, что эти свойства могли быть не инициализированы при вызове команды. Для этих целей в качестве значений по умолчанию для этих свойств использованы объекты – «заглушки». Они ничего не делают сами по себе, но позволяют разработчику избегать проверок свойства на null каждый раз, когда необходимо выполнить вывод данных.

Код реализации команды при использовании указанных абстрактных классов размещается в абстрактном методе ExecuteCore.

Если разработчик команд введет дополнительные проверки параметров при реализации команды в методе ExecuteCore, то в случае обнаружения некорректных параметров необходимо также генерировать исключения только вышеупомянутых типов или их наследников.

Утилита для запуска команд, предоставленных командными модулями

Утилита должна быть сконфигурирована индивидуально под нужды конкретного приложения. Конфигурация осуществляется посредством стандартного конфигурационного файла RunCmd.exe.config.
Настройка подсистемы безопасности выполняется в секции <securityConfiguration>. Утилита должна поддерживать тот же механизм аутентификации, что и основное приложение с которым утилита поставляется и с БД которого взаимодействуют команды. Приложения могут использовать различные механизмы аутентификации. Это и различные классы аутентификации: AuthenticationStandard, AuthenticationActiveDirectory, XafariAuthentication, и различные классы пользователей и ролей: SecuritySystemRole, SecuritySystemUser, Xafari.Northwind.Security.InheritedRole, Xafari.Northwind.Security.InheritedUser, различные типы логон-параметров: XafariAuthenticationLogonParameters.

В свойстве securityAssemblies через точку с запятой должны быть перечислены сборки, в которых находятся используемые при аутентификации типы подсистемы безопасности (классы аутентификации, классы пользователей, ролей, классы логон-параметров). Если указанные сборки являются модулями XAF, то их также необходимо указать в ключе Modules секции <appSettings>.

Посредством необязательных свойств roleTypeName, userTypeName при необходимости указываются частные реализации классов ролей и пользователей (наименование класса с указанием пространства имен). Эти значения используются консольной утилиты при инициализации свойств SecurityStrategyComplex.RoleType и SecurityStrategyComplex.UserType соответственно.

Секция <authentificationClass> описывает класс аутентификации. Свойству typeName присваивается наименование класса с указанием пространства имен. Утилита создает экземпляр указанного класса и использует его для установки свойства SecurityStrategyComplex.Authentication. Для создания экземпляра класса аутентификации может потребоваться инициализация некоторых его свойств. Для этих целей в секцию <authentificationClass> введена подсекция <properties>. Наименования и значения инициализируемых свойств указываются путем добавления элементов <add name="…" value="…">. Для генерации значений, указанных в свойстве value в коде утилиты используется метод System.ComponentModel.TypeConverter.ConvertFromString для типа инициализируемого свойства. Если инициализируемое свойство имеет тип System.Type, для генерации его значение создается экземпляр указанного в value класса.
В секции <connectionStrings>, указывается строка подключения к БД так же, как и у основного приложения.
В секции <applicationSettings> параметру DbApplicationName должно быть присвоено наименование основного приложения, работающего с БД. Это имя используется при проверке совместимости с БД.

Модули, непосредственно реализующие различные команды должны быть перечислены в ключе Modules раздела <appSettings>.

При первом запуске утилиты может возникнуть исключение несовместимости с БД. В этом случае необходимо выполнить встроенную в утилиту команду обновления БД (использовав ключ командной строки /dbupdate, описанный в следующем параграфе). Стандартная утилита DBUpdater по отношению к RunCmd.exe некорректно работает на новой БД. Если БД уже использовалась, то для приведения базы в согласованное состояние можно использовать и DBUpdater.

Ключи командной строки утилиты для запуска команд

Любой из ключей:
 /? 
 /h 
 /help 
служи для вывода на экран краткой справки по использованию утилиты.

Список доступных для исполнения команд можно получить, запустив утилиту с любым ключей:
 /cmdlist 
 /s 

Описание параметров указанной команды выводятся при использовании ключей:
 /paramlist <команда> 
 /p <команда> 

Для выполнения произвольной команды ее наименование и параметры в виде разделенных двоеточием пар имя:значение :
 /command имя_команды параметр1:значение1 параметр2:значение2 … 
 /c имя_команды параметр1:значение1 параметр2:значение2 … 

Если команда задана в качестве первого аргумента командной строки, ключ /command или /c можно не указывать.
В командной строке утилиты может быть указано несколько команд. В этом случае все они будут выполнены в том порядке, в котором они перечислены.

Существует возможность задать список команд в текстовом файле, имя которого передается утилите в качестве параметра ключа:
 /batch <имя_файла> 
 /b <имя_файла> 

Каждая команда со своими параметрами занимает одну строку в текстовом файле. Команды должны иметь следующий вид:
 имя_команды1 параметр1:значение1 параметр2:значение2 … 
 имя_команды2 параметр1:значение1 параметр2:значение2 … 
 … 

Для указания параметров аутентификации служит ключ
 /logon параметр1:значение1 параметр2:значение2 … 
 /l параметр1:значение1 параметр2:значение2 … 

Передаваемые в командной строке разделенные двоеточием пары параметр:значение утилита интерпретирует как имена и значения свойств класса, описывающий логон-параметры (например, свойства класса XafariAuthenticationLogonParameters). Утилита создает экземпляр класса логон-параметров, устанавливает его свойства в указанные в командной строке значения и использует полученный экземпляр при аутентификации.

Различные аргументы командной строки можно также перечислить в произвольном текстовом файле. Команды могут располагаться как в одной строке, так и в нескольких строках файла. Для использования возможности чтения аргументов из файла служит ключ
 @<имя_файла> 

Таких ключей в командной строке утилиты может быть несколько.
При возникновении рассогласованности с базой данных необходимо воспользоваться ключом
 /dbupdate [silent] 

При этом будет выполнена стандартная процедура проверки совместимости БД и выполнено обновление. Необязательный параметр silent позволяет провести обновление без участия пользователя.