1 лицензия = 1000 пользователей. Реально!

29 сентября 2013

Файлы для загрузки

Проект ASP.NET-приложения, пример из статьи.

Предисловие

Перед нами встала задача - в некоторых подразделениях компании сотрудники должны видеть текущие заказы клиентов и их состав в режиме реального времени. Отображать эти данные будут установленные в помещениях (например, на складах или в службе закупок) дисплеи (табло). Все необходимые данные находятся в используемой информационной системе "Управление производственным предприятием" на базе платформы 1С:Предприятие 8.

Почему просто не предоставить пользователям доступ к программе, где они смогут просматривать отчет по текущим остаткам заказов покупателей? Ответ очевиден - деньги. Что если пользователей больше 1000 и не все они работают с программой УПП? Открывать доступ только для просмотр отчета? 

No key!Почему просто не предоставить пользователям доступ к программе, где они смогут просматривать отчет по текущим остаткам заказов покупателей? Ответ очевиден - деньги. Что если пользователей больше 1000 и не все они работают с программой УПП? Открывать доступ только для просмотр отчета? 

Стоимость лицензии 1С:Предприятие 8 на одно рабочее место в среднем составляет от 2960 (если покупать пакет 500 лицензий) до 5200 рублей (если покупать одну лицензию). Тогда получается, что для решения задачи нужно потратить на приобретение 1000 лицензий минимум 3 млн. рублей!!!

Нас это не устраивает и мы находим другое техническое решение для решения задачи без нарушения лицензионного соглашения использования 1С:Предприятия и с существенно меньшим бюджетом! 

Создаем web-клиент

Мы будем создавать некоторое подобие web-клиента на базе ASP.NET, который будет работать через единственной подключение с сервером 1С:Предприятия посредством кэшируемого COM-соединения. Использованный способ поддержки COM-соединения описывался в одной из предыдущих статей, сейчас же мы сразу перейдем к его непосредственной реализации.

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

Остатки по заказам покупателей по данным информационной базы 1С:Предприятия 8

Страница будет обновляться каждые 10 секунд, поэтому пользователи, обрабатывающие заказы покупателей, будут всегда видеть оперативные данные. Для более наглядной демонстрации на web-страницу добавил кнопку "Обновить" для ручного обновления таблицы.

Те же данные в режиме 1С:Предприятия

Прежде чем переходить к непосредственному рассмотрению реализации, отметим, что в примере использовался веб-сервер Microsoft Internet Information Services (IIS) и среда разработки Microsoft Visual Studio Express 2012 для Web. Используемая версия платформы 1С:Предприятие - 8.2.17.153. На машине веб-сервера должна быть зарегистрирована компонента COM-соединения платформы 1С:Предприятие, которая идет в пакете с установкой платформы. Подробнее о настройке сервера IIS для работы с ASP.NETВы можете прочитать здесь. Что касается версии платформы .NET, на которой создавалось приложение ASP.NET в статье, то используемая версия 4.0.

Реализуем функционал

Создадим новый проект веб-сайта, добавив в него модуль глобального приложения "Global.ASAX". В этом модуле мы будем инициировать COM-соединение с информационной базой и сохранять его в глобальной переменной приложения. Но для начала определим в файле конфигурации "web.config" строку подключения к информационной базе 1С:Предприятия, которая будет использоваться при дальнейшей работе.

Объявление параметра приложения для строки подключения к информационной базе

Теперь в файле "Global.ASAX" в обработчике события "Application_Start" определим инициализацию COM-соединения с информационной базой и запись его в кэш приложения:

Code:

        protected void Application_Start(object sender, EventArgs e)
        {
            // Получаем строку подключения из конфигурационного файла
            string res = ConfigurationManager.AppSettings["ConnectionString"];
            // Формируем массив параметров
            string[] arg = new string[] { res };
            // Создаем COM-объект подключения к информационной базе 1С:Предприятия
            dynamic v82comConnector = Type.GetTypeFromProgID("V82.COMConnector");
            var v82 = Activator.CreateInstance(v82comConnector);
            // Устанавливаем соединение по полученной ранее строке подключения
            Application["Connnection"] = v82comConnector.InvokeMember("Connect",
                                                                      BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.Static,
                                                                      null,
                                                                      v82,
                                                                      arg);
        }

Теперь на любой web-странице приложения на стороне сервера мы можем обратиться к COM-соединению с информационной базой 1С:Предприятия.

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

Теперь обратимся к основной веб-странице нашего приложения "default.aspx". В конструкторе добавим на страницу кнопку "Update" (тип "Button") и элемент для просмотра данных "GridView". Изменим состав колонок GridView таким образом, чтобы они соответствовали следующему составу:

Состав колонок GridView

Тип всех колонок выбран как "BoundField".

Теперь создадим публичный метод "Updatetable()" заполняющий данными GridView. Как уже было сказано выше, данные будут получены непосредственно из информационной базы 1С:Предприятия через кэшированное COM-соединение.Тип колонок GridView

И так, последовательность действий метода будет следующая:

  1. Получаем сохраненное ранее COM-соединение.
  2. Получаем данные по остаткам регистра "Заказы покупателей" в составе полей, аналогичному составу полей GridView.
  3. Обновляем данные таблицы GridView на странице.

Все эти действия реализуются следующим программным кодом:

        public void UpdateTable()
        {
            // Получаем установленное ранее COM-соединение
            dynamic Connection = Application["Connnection"];
            // Формируем запрос платформы к базе данных
            dynamic Query = Connection.NewObject("Запрос");
            Query.Текст =
            "ВЫБРАТЬ\n" +
            "Представление(ЗаказыПокупателейОстатки.Номенклатура) Как Номенклатура,\n" +
            "ВЫБОР\n" +
            "КОГДА ЗаказыПокупателейОстатки.Номенклатура.Артикул = \"\"\n" +
            "ТОГДА \" - \"\n" +
            "ИНАЧЕ ЗаказыПокупателейОстатки.Номенклатура.Артикул\n" +
            "КОНЕЦ КАК Артикул,\n" +
            "ВЫБОР\n" +
            "КОГДА ЗаказыПокупателейОстатки.ХарактеристикаНоменклатуры = ЗНАЧЕНИЕ(Справочник.ХарактеристикиНоменклатуры.ПустаяСсылка)\n" +
            "ТОГДА \" - \"\n" +
            "ИНАЧЕ Представление(ЗаказыПокупателейОстатки.ХарактеристикаНоменклатуры)\n" +
            "КОНЕЦ КАК Характеристика,\n" +
            "ЗаказыПокупателейОстатки.КоличествоОстаток КАК Заказано,\n" +
            "ПРЕДСТАВЛЕНИЕ(ЗаказыПокупателейОстатки.ЗаказПокупателя) КАК ЗаказПокупателя,\n" +
            "ЗаказыПокупателейОстатки.ЗаказПокупателя.Дата КАК ЗаказПокупателяДата\n," +
            "ЗаказыПокупателейОстатки.ЗаказПокупателя.Номер КАК ЗаказПокупателяНомер\n" +
            "ИЗ\n" +
            "РегистрНакопления.ЗаказыПокупателей.Остатки КАК ЗаказыПокупателейОстатки\n" +
            "УПОРЯДОЧИТЬ ПО\n" +
            "ЗаказыПокупателейОстатки.ЗаказПокупателя.Дата";
            // Выполняем запрос
            dynamic Result = Query.Выполнить();
            // Создаем выборку запроса
            dynamic Choose = Result.Выбрать();
            // Создаем массив данных в качестве источника данных для GridView
            ArrayList DataSource = new ArrayList();
            // Заполняем массив данными выборки
            while (Choose.Следующий())
            {
                // В массив добавляются объекты класса Item. Этот класс создан нами
                // специально для формирования записей с аналогичной структурой колонок 
                // GridView
                DataSource.Add(new Item() { Nomenclatura = Choose.Номенклатура, 
                                            Articul = Choose.Артикул, 
                                            Haracteristica = Choose.Характеристика, 
                                            Zakazano = Choose.Заказано, 
                                            ZakazPokupatelya = Choose.ЗаказПокупателя,
                                            DocDate = Choose.ЗаказПокупателяДата.ToString(),
                                            DocNumber = Choose.ЗаказПокупателяНомер.ToString()
                });
            }
            // Обновляем данные в GridView
            GridView1.DataSource = DataSource;
            GridView1.DataBind();
        }
Все достаточно подробно прокомментировано. Обратите внимание на добавляемые в массив-источник данных объекты "Item". Это наш собственный класс. Вот его описание:
        protected class Item
        {
            public bool Set { get; set; }
            public string Nomenclatura { get; set; }            
            public string Articul { get; set; }
            public string Haracteristica { get; set; }
            public string ZakazPokupatelya { get; set; }
            public int Zakazano { get; set; }
            public string DocDate { get; set; }
            public string DocNumber { get; set; }
        }

Обратите внимание, что имена общедоступных полей класса соответствую именам полей данных элемента страницы GridView.

Метод обновления данных таблицы GridView на странице готов. Вызывать мы его будем в двух случаях: при загрузке страницы в событии "Page_Load" и при нажатии на добавленную нами кнопку "Update".

В начале описания задачи было сказано, что страница должна обновляться автоматически каждые 10 секунд, чтобы пользователи всегда видели только актуальные данных. Для соблюдения условия откроем редактор HTML-кода ASP страницы "default.aspx" и в секцию

<metahttp-equiv="refresh"content="10">

Теперь страница будет обновляться автоматически каждые 10 секунд.

Осталось протестировать наше решение.

Тестирование

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

Параллельное получение данных информационной базы ASP.NET-приложением через кэшируемое COM-соединение

Время открытия страницы варьируется от 40 до 50 миллисекунд. Лишь при открытии COM-соединения в первый раз, когда происходящий инициализация COM-соединения с информационной базой, время открытия страницы может достигать 20 секунд.

Время открытия страницы

Если посмотреть количество активных сеансов в информационной базе, то не зависимо от количество открытых страниц в браузере или количества обращений - всегда будет только один активный сеанс (соединение) на сервере 1С:Предприятия, через которое ASP.NET-приложение получает данные.

Активный сеанс на сервере 1С:Предприятия, используемый ASP.NET - приложением

Ну а один сеанс - одна лицензия! Задача выполнена!

Делайте выводы

Добавлена кнопка "Перепровести" для перепроведения заказа покупателя в текущей строкеТеперь мы имеем практически неограниченные возможности работы с данными информационной базы 1С:Предприятия описанным выше способом. При этом тратиться мы будем одну единственную лицензию на сеанс COM-соединения. Не являюсь юристом, но не вижу никаких причин говорить, что данный способ взаимодействия с платформой каким-либо образом нарушает лицензионное соглашение с фирмой "1С" по использованию их продукта.

Хоть в примере выше мы обошлись лишь чтением данных, но нам ничего не мешает создать полноценное ASP.NET-приложение для работы с определенными документами или справочниками, чтобы пользователи работали только через web-интерфейс. Например, добавив в GridView колонку с типом "ButtonField" на которой будет находиться кнопка "Перепровести" (имя команды установим "RePost"). 

По нажатию кнопки срабатывает событие элемента GridView "RowCommand". Вот программный код этого обработчика для перепроведения заказа покупателя из выбранной строки:

        protected void GridView1_RowCommand(object sender, GridViewCommandEventArgs e)
        {
            // Проверяем имя обрабатываемой команды
            if (e.CommandName == "RePost")
            {
                // Получаем номер и дату заказа покупателя из текущей строки
                int index = Convert.ToInt32(e.CommandArgument);
                GridViewRow selectedRow = GridView1.Rows[index];
                string Number = selectedRow.Cells[5].Text;
                DateTime Date = DateTime.Parse(selectedRow.Cells[4].Text);
                // Получаем кэшируемое COM-соединение с информационной базой 1С:Предприятия
                dynamic Connection = Application["Connnection"];
                // Выполняем запрос платформы для поиска документа "Заказ покупателя" по номеру и дате
                dynamic Query = Connection.NewObject("Запрос");
                Query.Текст =
                "ВЫБРАТЬ ЗаказПокупателя.Ссылка ИЗ Документ.ЗаказПокупателя КАК ЗаказПокупателя ГДЕ ЗаказПокупателя.Номер = \""
                + Number + "\" И ЗаказПокупателя.Дата =  ДАТАВРЕМЯ("+Date.Year+","+Date.Month+","+Date.Day+","+Date.Hour+","+Date.Minute+","+Date.Second+")";                
                dynamic Result = Query.Выполнить();
                dynamic Choose = Result.Выбрать();
                if (Choose.Следующий())
                {
                    dynamic Link = Choose.Ссылка;
                    try
                    {
                        // Если документ найден - перепроведем его
                        Link.ПолучитьОбъект().Записать(Connection.РежимЗаписиДокумента.Проведение);                        
                    } catch
                    {
                            // Нет действий
                    }
                    // Обновим данные в элементе GridView
                    UpdateTable();
                }
            }
        }

Аналогичным образом можно создать любой функционал, поддерживаемый платформой и работать с ним из ASP.NET-приложения.

Однако тут встает другой вопрос - затраты. Если нам нужно создать ASP.NET-приложение, работающее с информационной базой 1С:Предприятия, которое будет иметь небольшой функционал, то решение задачи оправдает себя. Небольшой срок выполнения и относительно малые трудозатраты на ее реализацию выйдут намного меньше, чем покупка клиентский лицензий 1С:Предприятия.

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

В последнем случаем выгоднее либо купить лицензии на платформу 1С:Предприятие. А в некоторых ситуациях правильнее задуматься об реализации собственной информационной системы. Например, на базе той же платформы .NET или с использованием MONO. Хоть это и потребует значительных инвестиций в начале, но в перспективе обойдется дешевле, чем покупка большого количества клиентский лицензий для платформы 1С:Предприятие.

Однажды зайдя в магазин DNS для покупки внешнего винчестера заметил, что раньше компания использовала платформу 1С:Предприятие (возможно конфигурацию "Управление торговлей") для ведения учета, а сейчас же на всех мониторах менеджеров красуется новое, возможно самописное, ASP.NET-приложение. Вот и пример из реальной жизни =).


comments powered by Disqus