Прямая запись в справочник 1C: Предприятие через Linq на примере работы с пользователями Asp.Net

  • Tutorial

Прямая запись в справочник 1C: Предприятие через Linq на примере работы с пользователями Asp.Net


Статья описывает как зарегистрировать пользователя веб-сайта в справочнике 1С: Предприятие 8, расположенном в MSSQL базе данных. Далее пользователь может авторизоваться на сайте, используя логин и пароль, указанный при регистрации. Работа ведется только со справочником с названием Пользователи и не затрагивает систему работы с пользователями через конфигуратор 1С.

Описаны только простейшие операции по регистрации и авторизации. Вспомогательные операции по восстановлению пароля, информированию через E-mail не освещаются. Работа ведется прямым доступом к базе MSSQL через Linq. Подход позволяет использовать одновременно функционал Asp.Net и 1С, а также обойтись без посредников в виде разных CMS.



Создание справочника Пользователи в 1С: Предприятие


Предполагается, что информационная база 1С создана и расположена на MSSQL-сервере в локальной сети или удаленно у хостинг-провайдера.

Необходимо через конфигуратор создать справочник Пользователи с кодом в виде строки 9 символов и наименованием 25 символов.

Создание справочника Пользователи в 1С:Предприятие

Структура полей следующая (перечень полей взят с избытком для дальнейшего расширения функциональности):
  • Активный: Булево
  • ДатаРегистрации: Дата
  • Email: Строка 255, переменная
  • Пароль: Строка 32 переменная
  • ПарольПрефикс: Строка 16 переменная
  • Язык: Строка 255 переменная
  • ФорматДаты: Строка 20 переменная
  • КодАктивации: Строка 36 переменная
  • КодВосстановленияПароля: Строка 36 переменная
  • ДатаАктивности: Дата


Интерфейс созданного справочника сразу доступен из 1С, что позволяет сэкономить время на создании интерфейса администрирования. Это одно из достоинств способа, когда доступен весь арсенал средств 1С: Предприятие. К другим достоинствам можно отнести: пометка на удаление без физического удаления, фильтры и поиск, возможность построения запросов СКД, обмен с другими базами 1С через конвертацию данных и т.д.

Например, доступен список зарегистрированных пользователей сразу после определения справочника в конфигураторе:

Доступен список зарегистрированных пользователей сразу после определения справочника в конфигураторе

А также форма редактирования пользователя:

Интерфейс формы редактирования пользователя

Настройка прямого доступа к базе данных MSSQL



Для доступа к данным нужно сгенерировать файл cs с определениями LINQ. Для генерации нужно воспользоваться утилитой LinqTo1C, указав какие справочники нужно выгружать, а также строку подключения к MSSQL базе данных. Для нашего случая достаточно выгрузить только Справочник.Пользователи.

Начиная с версии 1.2, LinqTo1C позволяет создавать конфигурационный файл с возможностью изменения данных. Не смотря на достоинства прямой записи в базу данных, у способа есть недостатки. Например, записанная таким способом информация не будет автоматически зарегистрирована в планах обмена. Вам самостоятельно нужно заботиться о поддержании правильности данных: генерировать уникальный код, поддерживать целостность с другими объектами, проверять вновь вносимые данные.

Утилита LinqTo1C

В результате работы утилиты появятся 2 файла: dbml – для визуального представления и cs-для добавления в проект C#. Выглядит в редакторе Visual Studio это так:

Результат работы LinqTo1C в Visual Studio

Asp.Net код для регистрации и авторизации


Код приведен для Asp.Net MVC, где за работу пользователя отвечает AccountController.

AccountController:
public ActionResult LogOn()
        {
            ViewBag.Title = Resources.Account.LogonTitle;

            return View("~/Views/Dotnet/Logon.cshtml");
        }

        [HttpPost]
        public ActionResult LogOn(LogOnModel model, string returnUrl)
        {
            if (ModelState.IsValid)
            {
                if (MembershipService.ValidateUser(model.UserName, model.Password))
                {
                    FormsService.SignIn(model.UserName, model.RememberMe);
                    if (Url.IsLocalUrl(returnUrl))
                    {
                        return Redirect(returnUrl);
                    }
                    else
                    {
                        return RedirectToAction("Index", "Dotnet");
                    }
                }
                else
                {
                    ModelState.AddModelError("", "");
                }
            }

            // If we got this far, something failed, redisplay form
            return View("~/Views/Dotnet/Logon.cshtml", model);
        }

        public ActionResult LogOff()
        {
            FormsService.SignOut();

            return RedirectToAction("Index", "Dotnet");
        }

        public ActionResult Register()
        {
            ViewBag.Title = Resources.Account.RegisterTitle;

            ViewBag.PasswordLength = MembershipService.MinPasswordLength;
            return View("~/Views/Dotnet/Register.cshtml");
        }

        [HttpPost]
        public ActionResult Register(RegisterModel model)
        {
            if (ModelState.IsValid)
            {
                // Attempt to register the user
                MembershipCreateStatus createStatus = MembershipService.CreateUser(model.UserName, model.Email, model.Password, model.ConfirmPassword);

                if (createStatus == MembershipCreateStatus.Success)
                {
                    FormsService.SignIn(model.UserName, false /* createPersistentCookie */);
                    return RedirectToAction("Index", "Dotnet");
                }
                else
                {
                    ModelState.AddModelError("", AccountValidation.ErrorCodeToString(createStatus));
                }
            }

            // If we got this far, something failed, redisplay form
            ViewBag.PasswordLength = MembershipService.MinPasswordLength;
            return View("~/Views/Dotnet/Register.cshtml", model);
        }


Вызовы вида FormsService.SignOut и FormsService.SignIn не такие интересные, так как они перенаправляются к стандартным методам: FormsAuthentication.SignOut и FormsAuthentication.SetAuthCookie.

Класс AccountMembershipService осуществляет обращение к базе MSSQL.

Для регистрации пользователя предназначен метод CreateUser. Сначала проверяются возможные ошибки: пустой логин, пароль, e-mail, дублирующийся логин/e-mail, совпадение пароля и подтверждение пароля. Далее заполняются поля пользователя. Ссылке присваивается новый Guid, номер ищется как максимальный номер + 1. Пароль хранится в виде контрольной суммы в закрытом, контрольная сумма вычисляется от пароля пользователя и уникального префикса, который хранится здесь же в записи.
public MembershipCreateStatus CreateUser(string userName, string email, string password, string confirmPassword)
        {
            if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");
            if (String.IsNullOrEmpty(password)) throw new ArgumentException("Value cannot be null or empty.", "password");
            if (String.IsNullOrEmpty(email)) throw new ArgumentException("Value cannot be null or empty.", "email");

            MembershipCreateStatus status = MembershipCreateStatus.ProviderError;

            using (var dataContext = new ElisyCMS(ConfigurationManager.ConnectionStrings["ElisyCMS"].ConnectionString))
            {
                if (dataContext.СправочникПользователи.Where(m => m.Наименование == userName && m.ПометкаУдаления == new Binary(new byte[]{0})).Count() != 0)
                    return MembershipCreateStatus.DuplicateUserName;
                if (dataContext.СправочникПользователи.Where(m => m.Email == email && m.ПометкаУдаления == new Binary(new byte[] { 0 })).Count() != 0)
                    return MembershipCreateStatus.DuplicateEmail;
                if (password != confirmPassword)
                    return MembershipCreateStatus.InvalidPassword;

                try
                {
                    СправочникПользователи user = new СправочникПользователи();

                    user.Ссылка = Guid.NewGuid().ToByteArray();
                    user.ПометкаУдаления = new byte[] { 0 };
                    user.Предопределенный = new byte[] { 0 };

                    var codeRequest = from a in dataContext.СправочникПользователи
                                      where Convert.ToInt32(a.Код) > 0
                                      orderby Convert.ToInt32(a.Код) descending
                                      select Convert.ToInt32(a.Код);
                    var lastCode = codeRequest.Take(1).FirstOrDefault();
                    user.Код = (lastCode + 1).ToString().PadLeft(9, '0');

                    user.Наименование = userName;

                    byte[] saltBytes = new byte[8];
                    new RNGCryptoServiceProvider().GetBytes(saltBytes);
                    user.ПарольПрефикс = Convert.ToBase64String(saltBytes);

                    byte[] passwordBytes = System.Text.Encoding.UTF8.GetBytes(user.ПарольПрефикс + password);
                    byte[] hash = new SHA1CryptoServiceProvider().ComputeHash(passwordBytes);
                    user.Пароль = Convert.ToBase64String(hash);

                    user.ДатаРегистрации = DateTime.Now;
                    user.ДатаАктивности = DateTime.Now;
                    user.Активный = new Binary(new byte[] { 1 });
                    user.Язык = System.Threading.Thread.CurrentThread.CurrentUICulture.Name;
                    //user.КодАктивации = Guid.NewGuid().ToString();

                    user.Email = email;

                    dataContext.СправочникПользователи.InsertOnSubmit(user);
                    dataContext.SubmitChanges();

                    return MembershipCreateStatus.Success;
                }
                catch (Exception ex)
                {
                    return MembershipCreateStatus.ProviderError;
                }
            }

            return status;
        }
          

Для авторизации предназначен метод ValidateUser, который после проверки параметров проверяет наличие пользователя в базе, флаг активности и пытается сравнить контрольную сумму пароля с вычисленной контрольной суммой переданного пароля и уникального префикса.
        public bool ValidateUser(string userName, string password)
        {
            if (String.IsNullOrEmpty(userName)) throw new ArgumentException("Value cannot be null or empty.", "userName");
            if (String.IsNullOrEmpty(password)) throw new ArgumentException("Value cannot be null or empty.", "password");

            using (var dataContext = new ElisyCMS(ConfigurationManager.ConnectionStrings["ElisyCMS"].ConnectionString))
            {
                var пользователь = dataContext.СправочникПользователи.Where(m => m.Наименование.ToUpper() == userName.ToUpper()).FirstOrDefault();
                if (пользователь == null)
                    return false;
                if (пользователь.Активный.ToArray()[0] == 0)
                    return false;
                if (String.IsNullOrWhiteSpace(пользователь.Пароль))
                    return true;
                byte[] passwordBytes = System.Text.Encoding.UTF8.GetBytes(пользователь.ПарольПрефикс + password);
                byte[] hash = new SHA1CryptoServiceProvider().ComputeHash(passwordBytes);
                return Convert.ToBase64String(hash).Equals(пользователь.Пароль);
            }
        }
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 19

    +1
    Судя по dbml-файлу, это, на самом деле, Linq2Sql, так? Или все-таки какой-то собственный провайдер?

    А теперь скажите мне, в чем смысл? С точки зрения дизайна приложения приведенные вами примеры очень плохи. С точки зрения технологии ничего особо нового они тоже не представляют — обычная интеграция через базу данных, имя которым легион (и которые уже давно перестали быть рекомендуемым подходом). Так зачем?
      0
      В основу положена технология LinqToSql, а не собственный провайдер.
      Не совсем понятно, в чем «плохость» примера. В статье показан именно такой способ доступа. Все понимают, что у любого подхода есть как достоинства, так и недостатки. В некоторых случаях прямой доступ к 1С оправдан, в других – нет.
      В том, что особо нового ничего нет — не соглашусь. Статья показывает, как можно получать и обновлять данные в 1С напрямую. Раньше был достаточно хорошо исследован способ только по получению данных. Возможность вменяемого обновления – это новизна.
      Смысл подхода — хранить все данные централизованно в 1С без экспорта/импорта в сторонние CMS, экономить время на создании административного интерфейса, экономить на MSSQL, размещенном удаленно у провайдера.
        0
        Не совсем понятно, в чем «плохость» примера.

        В нарушении принятых «хороших» практик программирования (например, Dependency Inversion). Ваш код тестируем?

        Статья показывает, как можно получать и обновлять данные в 1С напрямую.

        Не показывает. Статья показывает, как обновлять данные в Linq2Sql-датаконтексте, и это, извините, не новость вообще. А вот как устроен Linq2Sql-датаконтекст, что он может работать с БД 1С мы как раз и не видим, так что работа с 1С никак не показана.
          0
          Мой код тестируем, не понятна причина сомнений в тестируемости. То, что подход не попадает под какие-то практики программирования я допускаю, но я также допускаю, что он попадает под другие практики программирования. Думаю, практики – это не очень хороший критерий оценки, потому что субъективны, они могут меняться со временем на противоположные. Давайте оперировать более измеряемыми величинами: временем и деньгами, а философию оставим для других статей и разделов. Подход дает экономию времени, и стоимости. И если вы придете к клиенту и скажете есть решение, позволяющее сэкономить, но нарушающее практику «Dependency Inversion», думаю, что с нарушением этой практики клиент смирится. Интересно, кстати, было бы посмотреть, как вы клиенту будете объяснять, почему он должен доплачивать за точное соблюдение этой практики )))).

          То, что все умеют обновлять данные в Linq2Sql – это не новость, но новость заключается в том, что обновление идет данных 1С, которая славится запутанностью своей структуры. И без статьи это обновление займет значительно больше времени, пока вы разберетесь с названиями таблиц и полей.
          image

            +1
            Мой код тестируем, не понятна причина сомнений в тестируемости.

            Причина очень проста: как вы будете тестировать, скажем, класс AccountMembershipService (не обращаясь к БД, конечно же)?

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

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

            Интересно, кстати, было бы посмотреть, как вы клиенту будете объяснять, почему он должен доплачивать за точное соблюдение этой практики

            Такие вещи не объясняются клиенту. Такие вещи объясняются руководителю проекта и вносятся в технические расходы.

            И без статьи это обновление займет значительно больше времени, пока вы разберетесь с названиями таблиц и полей.

            Статья не объясняет, как это происходит. Статья говорит «возьмите утилиту и получите код». Как он работает — не описано.
      0
      Причина очень проста: как вы будете тестировать, скажем, класс AccountMembershipService (не обращаясь к БД, конечно же)?

      Структура классов и методов один в один взята из стандартного проекта Asp.Net MVC 3 из Visual Studio 2010, чтобы разработчики, знакомые с Asp.Net MVC нашли знакомые точки соприкосновения. Поэтому вопрос тестирования конкретного класса AccountMembershipService лучше адресовать не ко мне, а к Microsoft. Если не ошибаюсь, проект Asp.Net MVC в Visual Studio можно создать с включенными классами тестов и ознакомиться с тем, как Microsoft предлагает решить эту проблему. Если кому-то не понравится структура классов, он может сделать свою, это никак не повлияет на описанный метод.

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

      Предложите альтернативный подход для сравнения, подходящий под ваши критерии и практики. Будем сравнивать конкретные вещи, а не просто данный подход с неизвестным идеальным.

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

      Статья не объясняет, как это происходит. Статья говорит «возьмите утилиту и получите код». Как он работает — не описано.

      Вы противоречите себе. С одной стороны вы не находите ничего нового в технологии LinqToSql, но требуете досконального описания этой технологии. Код, получаемый в результате работы утилиты ничем не отличается от стандартного кода для доступа к обычным MSSQL таблицам, получаемым на основе dbml-файлов в Visual Studio. Заслуга утилиты — найти соответствие между человекочитаемыми названиями и непонятными названиями таблиц и полей.
        0
        Поэтому вопрос тестирования конкретного класса AccountMembershipService лучше адресовать не ко мне, а к Microsoft.

        Ну то есть вы не знаете, как. В этом и претензия — ваш код не пригоден для автоматического тестирования, поздравляю.

        Предложите альтернативный подход для сравнения, подходящий под ваши критерии и практики. Будем сравнивать конкретные вещи, а не просто данный подход с неизвестным идеальным.

        IoC во главе угла.

        Заслуга утилиты — найти соответствие между человекочитаемыми названиями и непонятными названиями таблиц и полей.

        Вот как она это делает — и надо описывать. Все остальное — скучно и не ново.
          0
          Ну то есть вы не знаете, как. В этом и претензия — ваш код не пригоден для автоматического тестирования, поздравляю.

          Я не задавался целью охватить тестами весь код. То, что код не пригоден для автоматического тестирования — голословное обвинение. На любой алгоритм статьи можно написать код проверки, зная, какой результат ожидается от этого метода.
          IoC во главе угла.

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

          Да ну?! То есть более-менее вменяемые методы прямой записи в базу 1С всем давно известны?
            0
            Я не задавался целью охватить тестами весь код.

            Прекрасно. А сколько из вашего (приведенного в статье) кода охвачено тестами?

            То, что код не пригоден для автоматического тестирования — голословное обвинение.

            Ну, оно вполне доказуемо. Единственное, что изначально я говорил о тестируемости кода, и имел в виду изолированное тестирование.

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

            А это как раз очень легко сравнимые вещи. Прямо в числовом выражении. В вашей статье, если я не ошибаюсь, описано семь методов. Сколько из них можно покрыть юнит-тестами при условии сохранения используемого в статье подхода управления зависимости, и сколько — при использовании IoC?

            То есть более-менее вменяемые методы прямой записи в базу 1С всем давно известны?

            Вы же сам писали «Код, получаемый в результате работы утилиты ничем не отличается от стандартного кода для доступа к обычным MSSQL таблицам». Следовательно, метод прямой записи в базу 1С — это стандартная работа с БД MSSQL (о чем, снова, пишете вы сам: «В основу положена технология LinqToSql»). Степень вменяемости этого метода ограничена степенью вменяемости Linq2Sql, которую мы тут обсуждать не будем.

              0
              Прекрасно. А сколько из вашего (приведенного в статье) кода охвачено тестами?

              Охвачено тестами ровно столько кода, сколько охвачено кода тестами при создании нового проекта Asp.Net MVC. Конкретное значение 20 или 80% что-то изменит? Демагогией не надоело заниматься?

              А это как раз очень легко сравнимые вещи. Прямо в числовом выражении. В вашей статье, если я не ошибаюсь, описано семь методов. Сколько из них можно покрыть юнит-тестами при условии сохранения используемого в статье подхода управления зависимости, и сколько — при использовании IoC?

              Если не надоело заниматься демагогией — продолжним. Жду от вас статьи, оформленной по всем хорошим общепринятым практикам программирования, с учетом IoC, покрытую 100% тестами, в т.ч. изолированными, решающую схожую проблему: прямая регистрация пользователей в базе 1С и авторизация. Дальше продолжим сравнение моего и вашего подходов не на пальцах. Вам отправить архив MSSQL или 1С?
                0
                Конкретное значение 20 или 80% что-то изменит?

                Конечно, изменит.

                Жду от вас статьи, оформленной по всем хорошим общепринятым практикам программирования, с учетом IoC, покрытую 100% тестами, в т.ч. изолированными, решающую схожую проблему: прямая регистрация пользователей в базе 1С и авторизация.

                Я правильно понимаю, что вы не можете ответить на озвученный мной вопрос в отношении вашего кода?
                  0
                  Я правильно понимаю, что вы не можете ответить на озвученный мной вопрос в отношении вашего кода?

                  Ответ на данный вопрос: «Сколько из них можно покрыть юнит-тестами при условии сохранения используемого в статье подхода управления зависимости, и сколько — при использовании IoC?» мы сможем получить после прочтения вашей статьи. Ведь каждый может вкладывать свой смысл в аббревиатуры. Я, например, в преддверии Олимпиады расшифровываю ее как International Olimpyc Committee, а вы, вероятно, имеете ввиду что-то другое. Ситуацию прояснит ваш код, оформленный по общепринятым (непонятно кем) хорошим (непонятно почему) принципам программирования. Но сильно не переусердствуйте, потому что тема тестирования, если ей уделять много внимания, запутает благодарного читателя, и он может не понять суть решаемой проблемы. Ведь мы же не пишем статьи про тестирование, а нацелены на разработку.
                    0
                    Ответ на данный вопрос: «Сколько из них можно покрыть юнит-тестами при условии сохранения используемого в статье подхода управления зависимости, и сколько — при использовании IoC?» мы сможем получить после прочтения вашей статьи.

                    Это не один вопрос, а два. И на половину из него (сколько из них можно покрыть юнит-тестами при условии сохранения используемого в статье подхода управления зависимостями) вы можете ответить прямо сейчас… если, конечно, вы задумывались о тестируемости вашего кода.

                    Я, например, в преддверии Олимпиады расшифровываю ее как International Olimpyc Committee, а вы, вероятно, имеете ввиду что-то другое.

                    Я, несомненно, имею в виду что-то другое. То, что принято иметь в виду под аббревиатурой IoC на сайте, связанном с программированием.

                    Ведь мы же не пишем статьи про тестирование, а нацелены на разработку.

                    Во-первых, вы путаете тестирование и тестируемость. А во-вторых тестирование — неотъемлимая часть процесса разработки, так что его обязательно необходимо принимать во внимание.
          0
          Я Вас огорчу — при включении тестов при создании mvc3/4/5 в VS12/13 Вы получаете три суперсложных трёхстрочных заглушечных теста на ненулевой возврат контента про вызове методов Index, About, Contacts контроллера Home, не говорящих ни о чём.
          Кстати, если кто поделится хорошей литературой по msunit'у, в том числе авторизацию в веб, буду благодарен.
            0
            Кстати, если кто поделится хорошей литературой по msunit'у, в том числе авторизацию в веб, буду благодарен.

            А вам о чем конкретно? Потому что тестирование авторизации в веб слабо зависит от того, каким тестовым фреймворком вы пользуетесь, подходы идентичны (и обычно описываются в книге по фреймворку, например, в книжке по WebAPI немаленький такой раздел про то, как это тестировать).
              0
              мне — вообще о MS Unit — сам пишу на Wpf/Winforms/MVC но если пишу с горем пополам, то написать тест сложнее штатных — не зщнаю куда копат, к ниг по ms unit'у толи не нахожу толи в гугле забанили. хочется почитать при тестирование как десктоп так и веб приложений, но у меня веб приложение совсем нав авторизацию закрыто, поэтому надо и авторизацию во всех методах дописывать, на этом затык, а без тестов уже сложно контролировать что могло отвалиться при очередных правках.
                0
                Судя по тому, что вы пишете, вам надо начинать с самых азов. Повторюсь еще раз, бессмысленно читать о конкретном тестовом фреймворке, идеология у всех одинаковая. Читать надо о тестировании (чтобы не было ошибочных допущений «надо и авторизацию во всех методах дописывать») вообще. Я бы посоветовал The Art of Unit Testing и xUnit Test Patterns.
              0
              Спасибо, что проинформировали. Может еще просветите, какие затруднения вызовет написание тестов для методов, описанных в статье? Хоть убейте меня, не могу понять — если есть метод, известно, чем он занимается, что на входе, а что на выходе — почему нельзя проверить правильно ли он сработал или нет.
            0
            1С движется в сторону облегчения интеграции. В 8.3.5 должны появиться плюшки для еще более простой интеграции, назвали REST, но учитывая определенную размытость формулировки, я лучше приведу ссылки:

            v8.1c.ru/o7/201312rest/index.htm
            v8.1c.ru/o7/201312http/index.htm

            Only users with full accounts can post comments. Log in, please.