2 место Нестандартные подходы к разработке шаблонов

Discussion in 'Девятый конкурс статей' started by shtift, May 10, 2018.

  1. shtift

    shtift Client

    Joined:
    Jul 29, 2015
    Messages:
    149
    Likes Received:
    239
    Сегодня я хочу поговорить о наработках в области разработки шаблонов, которые облегчают жизнь как на стадии разработки и отладки, так и на стадии поддержки уже готового шаблона.

    Ускоряем разработку с методами расширения

    В каждом шаблоне нам приходится выполнять одни и те же действия. Типичный алгоритм:
    1. Загрузить страницу;
    2. Подождать пока страница полностью прогрузится;
    3. Найти элемент;
    4. Проверить, что элемент был найден;
    5. Кликнуть/получить значение/вызвать событие/что-то еще;
    6. Перейти к шагу 1 или 3.
    Обычно это выливается в простыни однотипного кода. Пример:
    Code (csharp):
    1. instance.ClearCache();
    2. instance.ClearCookie();
    3.  
    4. var tab = instance.ActiveTab;
    5. tab.Navigate("ya.ru");
    6.  
    7. if(tab.IsBusy)
    8. {
    9.      tab.WaitDownloading();
    10. }
    11.  
    12. var searchInput = tab.FindElementByXPath("//input", 0);
    13. if(searchInput.IsVoid)
    14. {
    15.      throw new Exception("Поле ввода запроса не найдено.");
    16. }
    17.  
    18. searchInput.SetValue("test request", "full", false);
    19.  
    20. var findButton = tab.FindElementByXPath("//button" , 0);
    21. if(findButton.IsVoid)
    22. {
    23.      throw new Exception("Кнопка \"Найти\" не найдена");
    24. }
    25.  
    26. findButton.Click();

    Данный код загружает страницу Яндекса, вводит запрос в поле для поиска и нажимает кнопку "Найти".
    После каждого поиска элемента сначала проверяем был ли он найден. В противном случае выбрасываем исключение с сообщением для упрощения отладки в будущем.

    С этим кодом, на мой взгляд, есть несколько проблем:
    1. Код неудобно писать;
    2. Код неудобно читать;
    3. Код громоздкий;
    4. Есть однотипные куски кода, которые можно и нужно переработать;
    Все эти проблемы можно решить с помощью методов расширения.

    Создаем метод расширения

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

    Чтобы расширить существующий тип своими методами нам нужно создать статический класс со статическими методами и первым аргументом в методе через ключевое слово this указать расширяемый тип.

    Вот так мы можем добавить в системный тип string свой метод, который будет конвертировать строку в число:
    Code (csharp):
    1. public static class StringExtensions
    2. {
    3.      public static int ToInt(this string str)
    4.      {
    5.           return int.Parse(value);
    6.      }
    7. }
    Теперь мы можем воспользоваться этим методом в любом месте нашей программы:
    Code (csharp):
    1. int azino = "777".ToInt();

    Перерабатываем код с Яндексом

    Начнем с этой части кода:
    Code (csharp):
    1. tab.Navigate("ya.ru");
    2.  
    3. if(tab.IsBusy)
    4. {
    5.      tab.WaitDownloading();
    6. }
    В шаблонах очень часто приходится использовать метод Navigate для загрузки страницы и я не помню ни одного случая, когда мне не требовалось бы сразу дождаться загрузки страницы. Так почему бы не вынести этот кусок кода в метод расширения типа Tab ?!

    Сказано – сделано.
    Создаем статический класс ZennoExstensions и добавляем в него метод расширения для типа Tab. Назовем этот метод NavigateAndWait и он будет принимать в параметры адрес и необязательный параметр реферер:
    Code (csharp):
    1. public static class ZennoExstensions
    2. {
    3.      public static Tab NavigateAndWait(this Tab tab, string url, string referer = "")
    4.      {
    5.          tab.Navigate(url, referer);
    6.          if(tab.IsBusy)
    7.          {
    8.             tab.WaitDownloading();
    9.          }
    10.          return tab;
    11.      }
    12. }
    Теперь вместо стандартного метода Navigate мы можем использовать наш тюнингованный:
    Code (csharp):
    1. var tab = instance.ActiveTab;
    2. tab.NavigateAndWait("ya.ru");
    Шесть строчек схлопнулось в одну и при этом читаемость кода, как минимум, не ухудшилась. Ну а то, что одну строчку проще написать, чем шесть и ежу понятно.

    Fluent Interface

    Обратите внимание, что метод NavigateAndWait возвращает объект Tab. А это значит, что теперь мы можем делать цепочки вызовов таким образом:
    Code (csharp):
    1. tab.NavigateAndWait("ya.ru")
    2.    .NavigateAndWait("mail.ru")
    3.    .NavigateAndWait("zennolab.com");
    Этот паттерн называется Fluent Interface. Суть в том, что на возвращаемом из метода объекте мы можем вызывать другой метод, объединяя их в такие вот цепочки. Более подробно можете почитать здесь.

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

    Перерабатываем поиск и проверку элементов

    В примере с Яндексом у нас идет поиск 2-х элементов. Это поле ввода поискового запроса и кнопка "Найти":

    Code (csharp):
    1. var searchInput = tab.FindElementByXPath("//input", 0);
    2. if(searchInput.IsVoid)
    3. {
    4.      throw new Exception("Поле ввода запроса не найдено.");
    5. }
    6.  
    7. //манипуляции с searchInput
    8.  
    9. var findButton = tab.FindElementByXPath("//button" , 0);
    10. if(findButton.IsVoid)
    11. {
    12.      throw new Exception("Кнопка \"Найти\" не найдена");
    13. }
    14.  
    15. //манипуляции с findButton

    Для начала сделаем метод расширения поиска элемента по XPath.

    Сейчас мне не нравится, что всегда приходится передавать вторым параметром номер совпадения в методе FindElementByXPath. В моих шаблонах в 99.9% случаев это всегда будет 0. Поэтому можно сделать этот параметр необязательным, чтобы лишний раз не указывать его. Если в ваших шаблонах ситуация иная то, можно пропустить этот шаг.

    Итак, добавляем в наш класс ZennoExtensions новый метод. Лучше для каждого расширяемого типа создавать отдельный класс, как на скрине. В статье я буду писать все одном классе для удобства.
    [​IMG]

    Метод будет принимать 2 параметра: xpath и номер совпадения. Мы не можем назвать наш метод FindElementByXPath, т.к. сигнатуры будут полностью совпадать. Поэтому назовем наш метод GetElementByXpath:
    Code (csharp):
    1. public static HtmlElement GetElementByXpath(this Tab tab, string xpath, int number = 0)
    2. {
    3.        return tab.FindElementByXPath(xpath, number);
    4. }

    Не отходя от кассы добавляем метод расширения для типа HtmlElement, который будет бросать исключение, если элемент не найден:
    Code (csharp):
    1. public static HtmlElement ThrowIfNull(this HtmlElement he,
    2.                                       string exceptionMessage = "HtmlElement не найден (IsVoid).")
    3. {
    4.     if (he.IsVoid)
    5.     {
    6.         throw new Exception(exceptionMessage);
    7.     }
    8.     return he;
    9. }

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

    Теперь мы можем использовать эти методы так:
    Code (csharp):
    1. tab.GetElementByXpath("//a")
    2.    .ThrowIfNull("Элемент не найден") //Сообщение можно не указывать, т.к. мы задали значение по умолчанию
    3.    .Click();

    Итого первоначальный код преобразился до такого:
    Code (csharp):
    1. instance.ClearCache();
    2. instance.ClearCookie();
    3.  
    4. var tab = instance.ActiveTab;
    5. tab.NavigateAndWait("ya.ru");
    6.  
    7. tab.GetElementByXpath("//input").ThrowIfNull("Поле ввода запроса не найдено.").SetValue("test request", "full", false);
    8. tab.GetElementByXpath("//button").ThrowIfNull("Кнопка \"Найти\" не найдена").Click();
    Такой код более читабельный, а главное его быстрее, проще и просто приятнее писать.
    Code (csharp):
    1. public static class ZennoExtensions
    2. {
    3.      public static Tab NavigateAndWait(this Tab tab, string url, string referer = "")
    4.      {
    5.          tab.Navigate(url, referer);
    6.          if(tab.IsBusy)
    7.          {
    8.             tab.WaitDownloading();
    9.          }
    10.          return tab;
    11.      }
    12.     public static HtmlElement GetElementByXpath(this Tab tab, string xpath, int number = 0)
    13.     {
    14.          return tab.FindElementByXPath(xpath, number);
    15.     }
    16.     public static HtmlElement ThrowIfNull(this HtmlElement he, string exceptionMessage = "HtmlElement не найден (IsVoid).")
    17.     {
    18.         if (he.IsVoid)
    19.         {
    20.             throw new Exception(exceptionMessage);
    21.         }
    22.         return he;
    23.     }
    24. }

    Какие еще методы расширения можно сделать?

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

    Code (csharp):
    1. public static class ProjectExtensions
    2. {
    3.     public static IZennoPosterProjectModel Debug(this IZennoPosterProjectModel project, string message, bool showInPoster = false)
    4.     {
    5.         project.SendInfoToLog(message, showInPoster);
    6.         return project;
    7.     }
    8.     public static IZennoPosterProjectModel Info(this IZennoPosterProjectModel project, string message, bool showInPoster = true)
    9.     {
    10.         project.SendInfoToLog(message, showInPoster);
    11.         return project;
    12.     }
    13.     public static IZennoPosterProjectModel Warning(this IZennoPosterProjectModel project, string message, bool showInPoster = true)
    14.     {
    15.         project.SendWarningToLog(message, showInPoster);
    16.         return project;
    17.     }
    18.     public static IZennoPosterProjectModel Error(this IZennoPosterProjectModel project, string message, bool showInPoster = true)
    19.     {
    20.         project.SendErrorToLog(message, showInPoster);
    21.         return project;
    22.     }
    23. }

    Теперь мы можем вместо методов Send***ToLog мы можем вызывать:
    Code (csharp):
    1. project.Debug("сообщение1"); //будет выведено информационное сообщение только в PM
    2. project.Info("сообщение2"); //будет выведено информационное сообщение в PM и ZP
    3. project.Warning("сообщение3"); //будет выведено предупреждение в PM и ZP
    4. project.Error("сообщение4"); //будет выведена ошибка в PM и ZP

    Можете также черпать идеи из документации к библиотеке ZennoExtensions.
    Вот код метода WaitFor, который реализован в библиотеке:
    Code (csharp):
    1. /// <summary>
    2. /// Выполняет ожидание, пока на странице не будет найден HTML элемент по указанному XPath, либо до истечения таймаута.
    3. /// </summary>
    4. /// <param name="tab"></param>
    5. /// <param name="xpath">XPath для поиска.</param>
    6. /// <param name="number">Номер совпадения.</param>
    7. /// <param name="timeout">Длительность поиска в миллисекундах. По умолчанию 5000.</param>
    8. /// <returns>Возвращает true, если элемент был найден.</returns>
    9. public static bool WaitFor(this Tab tab, string xpath, int number = 0, int timeout = 5000)
    10. {
    11.     if (string.IsNullOrWhiteSpace(xpath))
    12.     {
    13.         throw new ArgumentException("XPath должен быть задан.");
    14.     }
    15.  
    16.     for (var i = 0; i < timeout / 100; i++)
    17.     {
    18.         if (!tab.FindElementByXPath(xpath, number).IsVoid)
    19.         {
    20.             return true;
    21.         }
    22.         Thread.Sleep(100);
    23.     }
    24.  
    25.     return false;
    26. }
    27.  
    28. /// <summary>
    29. /// Выполняет ожидание, пока на странице не будет найден HTML элемент по указанном атрибутам, либо до истечения
    30. /// таймаута.
    31. /// </summary>
    32. /// <param name="tab"></param>
    33. /// <param name="tags">Список тегов. Если количество тегов больше одного, их необходимо разделить ";".</param>
    34. /// <param name="attrName">Имя атрибута.</param>
    35. /// <param name="attrValue">Значение атрибута.</param>
    36. /// <param name="searchKind">Тип поиска. Доступно: text, notext, regex.</param>
    37. /// <param name="number">Номер совпадения.</param>
    38. /// <param name="timeout">Длительность поиска в миллисекундах. По умолчанию 5000.</param>
    39. /// <returns>Тот же объект <see cref="Tab"/>для Fluent Interface</returns>
    40. public static Tab WaitFor(this Tab tab, string tags, string attrName, string attrValue, string searchKind,
    41.     int number = 0, int timeout = 5000)
    42. {
    43.     for (var i = 0; i < timeout / 100; i++)
    44.     {
    45.         if (!tab.FindElementByAttribute(tags, attrName, attrValue, searchKind, number).IsVoid)
    46.         {
    47.             return tab;
    48.         }
    49.         Thread.Sleep(100);
    50.     }
    51.  
    52.     return tab;
    53. }
    54.  
    55. /// <summary>
    56. /// Выполняет ожидание, пока предикат не вернет true, либо до истечения таймаута.
    57. /// </summary>
    58. /// <param name="tab"></param>
    59. /// <param name="predicate">Условное выражение.</param>
    60. /// <param name="timeout">Длительность проверки выражения в миллисекундах. По умолчанию 5000.</param>
    61. /// <returns>Тот же объект <see cref="Tab"/>для Fluent Interface</returns>
    62. public static Tab WaitFor(this Tab tab, Func<bool> predicate, int timeout = 5000)
    63. {
    64.     if (predicate == null)
    65.     {
    66.         throw new ArgumentNullException("predicate");
    67.     }
    68.  
    69.     for (var i = 0; i < timeout / 100; i++)
    70.     {
    71.         if (predicate.Invoke())
    72.         {
    73.             return tab;
    74.         }
    75.         Thread.Sleep(100);
    76.     }
    77.  
    78.     return tab;
    79. }
    Этот метод удобен в использовании при динамическом изменении страницы, когда нужно дождаться появления, исчезновения или изменения элемента. Первые две перегрузки метода WaitFor выполняют ожидание, пока не будет найден элемент, либо до истечения указанного времени. Третья перегрузка выполняет ожидание, пока переданный предикат не вернет true, либо до истечения указанного времени.

    Пример использования:
    Code (csharp):
    1. var tab = instance.ActiveTab;
    2. var isLoginButtonFinded = tab.WaitFor("//button[@id='login']", 0, 10000); //Ожидаем появления элемента на странице 10 секунд
    3.  
    4. if(!isLoginButtonFinded)
    5. {
    6.    tab.Refresh();
    7. }
    В видео рассматривается как применение методов расширения может выглядеть на практике.

     
    Last edited: May 10, 2018
    Metrix, lybimiy86, Emfortes and 50 others like this.
  2. shtift

    shtift Client

    Joined:
    Jul 29, 2015
    Messages:
    149
    Likes Received:
    239
    Облачные сервисы логирования

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

    Познакомимся с таким функционалом на примере сервиса sentry.io.

    1. Регистрируемся и создаем новый проект типа C#.
    2z3dINq[1].png

    2. Далее нам покажут пример использования библиотеки SharpRaven, которая взаимодействует с сервисом.

    Все довольно просто. Устанавливаем библиотеку из Nuget и создаем экземпляр класса RavenClient в конструктор которого передаем сгенерированную ссылку нашего проекта.
    Code (csharp):
    1. var ravenClient = new RavenClient("https://2a485ba182874f15b4ef5ce2b53e42db:[email protected]/1198228");

    Используем следующим образом:
    Code (csharp):
    1. try
    2. {
    3.     int i2 = 0;
    4.     int i = 10 / i2;
    5. }
    6. catch (Exception exception)
    7. {
    8.     ravenClient.Capture(new SentryEvent(exception));
    9. }

    В этом коде возникнет исключение из-за деления на ноль и будет произведено логирование. В личном кабинете мы увидим сообщение о произошедшем исключении:

    J3iwjso[1].png
    Можно открыть это сообщение и посмотреть более детальную информацию: уровень события (информационное, фатальное и т.п.), когда произошло, сообщение, теги и многое другое.

    Но не обязательно логировать только исключения. Мы можем логировать просто какую-то информацию:
    Code (csharp):
    1. var account = "79286662299";
    2. ravenClient.Capture(new SentryEvent("Аккаунт " + account +" был зарегистрирован."));
    Можно устанавливать уровень события для последующей фильтрации и настройки оповещений:
    Code (csharp):
    1. ravenClient.Capture(new SentryEvent("Произошла фатальная ошибка")
    2. {
    3.     Level = ErrorLevel.Fatal
    4. });
    Также функционал sentry.io позволяет гибко настроить оповещения о произошедших событиях. По умолчанию оповещения уже приходят на почту. Но также можно интегрировать другие сервисы.

    Например, можно добавить Slack. И после этого вы будете получать оповещения прямо в мессенджер:
    WYyPoNE[1].png

    Мы можем выставить различные правила оповещений. Например сделать, чтобы оповещения приходили только о фатальных событиях или же о тех, которые возникают более 10 раз в час и т.д. Более подробно можно почитать в официальной документации.
     
    Last edited: May 10, 2018
  3. shtift

    shtift Client

    Joined:
    Jul 29, 2015
    Messages:
    149
    Likes Received:
    239
    Использование Google-таблиц

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

    Вот несколько вариантов использования:
    • Хранение XPath, Css-селекторов. Представьте, что при изменении структуры сайта вам достаточно обновить данные в таблице и шаблон снова начнет работать. Это избавит от головной боли и вас, и ваших клиентов.
    • "Поставщик – потребитель". Часто шаблоны работают по принципу: один шаблон регистрирует аккаунты, другой выполняет на них какую-то работу, а третий, например, может размораживать аккаунты, если они были временно заблокированы. Google-таблицы хорошо подходят для такой схемы обмена данными. Особенно если ваши шаблоны крутятся на разных серверах.
    • Оповещение пользователя. Еще один интересный вариант использования таблиц это оповещение пользователя о чем–либо. Например, при запуске шаблон может мониторить таблицу на предмет появления новой версии шаба и выдавать пользователю сообщение, что следует обновиться. Или же просто выдавать рекламу ваших новых продуктов.
    • Таблицы также умеют парсить сайты, а это значит, что мы можем агрегировать какую-либо информацию и скармливать её шаблону.

    Тестовый проект

    Создадим тестовый проект в котором будет реализована авторизация, чтения и запись данных.
    Действуем по документации.

    1. Первым делом нам нужно создать проект и включить Api. Переходим Google Api Console и создаем новый проект.
    DxKRBI6[1].png
    2. Далее нажимаем "Создать учетные данные".
    sjiXTgn[1].png
    3. Далее нажимаем "Отмена".
    IznAyDY[1].png
    4. Переходим на вкладку "Окно запроса доступа OAuth", вводим имя продукта и нажимаем "Сохранить".
    ELtpN2e[1].png
    5. Возвращаемся на вкладку "Учетные данные" и нажимаем "Создать учетные данные". В выпадающем списке выбираем пункт "Идентификатор клиента OAuth".
    alz5UMz[1].png
    6. Выбираем тип приложения "Другой" и вводим название.
    BUBQ5th[1].png
    7. Нажимаем "Создать" и скачиваем JSON-файл. Переименовываем этом файл в "client_secret.json"
    YneLpfV[1].png
    8. Создаем консольное приложение в Visual Studio и устанавливаем Nuget-пакет Google.Apis.Sheets.v4
    9. Добавляем в решение файл "client_secret.json" и устанавливаем в свойствах файла "Копировать в выходной каталог: Копировать всегда".
    gVFIAbH[1].png
    10. Замените содержимое файла Program.cs на следующее:
    Code (csharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.IO;
    4. using System.Threading;
    5. using Google.Apis.Auth.OAuth2;
    6. using Google.Apis.Services;
    7. using Google.Apis.Sheets.v4;
    8. using Google.Apis.Sheets.v4.Data;
    9.  
    10. namespace ConsoleApp1
    11. {
    12.     class Program
    13.     {
    14.         // If modifying these scopes, delete your previously saved credentials
    15.         // at ~/.credentials/sheets.googleapis.com-dotnet-quickstart.json
    16.         static string[] Scopes = { SheetsService.Scope.Spreadsheets };
    17.         static string ApplicationName = "ZennoTemplate";
    18.         static Random Random = new Random();
    19.  
    20.         static void Main(string[] args)
    21.         {
    22.             string spreadsheetId = "id вашей таблицы";
    23.             string range = "Accounts!A2:D";
    24.  
    25.             var service = Authorize();
    26.          
    27.             var newData = GenerateData();
    28.             AppendData(service, spreadsheetId, range, newData);
    29.  
    30.             var data = GetData(service, spreadsheetId, range);
    31.             PrintValues(data);
    32.             Console.Read();
    33.         }
    34.  
    35.         private static SheetsService Authorize()
    36.         {
    37.             UserCredential credential;
    38.  
    39.             using (var stream = new FileStream("client_secret.json", FileMode.Open, FileAccess.Read))
    40.             {
    41.                 string credPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
    42.                 credPath = Path.Combine(credPath, ".credentials/quickstart.json");
    43.                 credential = GoogleWebAuthorizationBroker
    44.                     .AuthorizeAsync(GoogleClientSecrets.Load(stream).Secrets, Scopes, "user", CancellationToken.None).Result;
    45.                 Console.WriteLine("Credential file saved to: " + credPath + Environment.NewLine);
    46.             }
    47.  
    48.             // Create Google Sheets API service.
    49.             var service =
    50.                 new SheetsService(new BaseClientService.Initializer()
    51.                 {
    52.                     HttpClientInitializer = credential,
    53.                     ApplicationName = ApplicationName,
    54.                 });
    55.             return service;
    56.         }
    57.  
    58.         public static IList<IList<object>> GetData(SheetsService service, string spreadsheetId, string range)
    59.         {
    60.             var request = service.Spreadsheets.Values.Get(spreadsheetId, range);
    61.  
    62.             var response = request.Execute();
    63.             return response.Values;
    64.         }
    65.  
    66.         public static void AppendData(SheetsService service, string spreadsheetId, string range, IList<IList<object>> values)
    67.         {
    68.             var body = new ValueRange{Values = values};
    69.  
    70.             var request = service.Spreadsheets.Values.Append(body, spreadsheetId, range);
    71.             request.InsertDataOption = SpreadsheetsResource.ValuesResource.AppendRequest.InsertDataOptionEnum.INSERTROWS;
    72.             request.ValueInputOption = SpreadsheetsResource.ValuesResource.AppendRequest.ValueInputOptionEnum.RAW;
    73.             request.Execute();
    74.         }
    75.  
    76.         public static void PrintValues(IList<IList<object>> values)
    77.         {
    78.             if (values == null || values.Count == 0)
    79.                 return;
    80.  
    81.             foreach (var row in values)
    82.             {
    83.                 Console.WriteLine("Сайт: {0} Дата добавления: {1} Аккаунт: {2}:{3}", row[0], row[1], row[2], row[3]);
    84.             }
    85.         }
    86.  
    87.         private static List<IList<object>> GenerateData()
    88.         {
    89.             var newData = new List<IList<object>>()
    90.             {
    91.                 new List<object> {"mail.ru", DateTime.Now.ToString("g"), $"login{Random.Next(0, 999)}", "password"},
    92.                 new List<object> {"mail.ru", DateTime.Now.ToString("g"), $"login{Random.Next(0, 999)}", "password"},
    93.                 new List<object> {"mail.ru", DateTime.Now.ToString("g"), $"login{Random.Next(0, 999)}", "password"}
    94.             };
    95.             return newData;
    96.         }
    97.  
    98.     }
    99. }
    11. Теперь создадим таблицу аккаунтов с которой будем работать.
    uKGZxFl[1].png

    12. Скопируйте из адресной строки идентификатор таблицы и присвойте его переменной spreadsheetId.
    oDZnmTP[1].png

    13. Запустите проект. После выполнения в таблицу добавятся новые данные.
    jHArZoY[1].png
     
    Last edited: May 10, 2018
  4. shtift

    shtift Client

    Joined:
    Jul 29, 2015
    Messages:
    149
    Likes Received:
    239
    Конкурсные статьи ограничены тремя темами на одного человека, но есть еще одна тема, которую я рассматриваю во внеконкурсных статьях это "Разработка шаблонов через VisualStudio". Рекомендую к ознакомлению.
     
    Last edited: May 10, 2018
    Redsmokky, Nike59, pg2016 and 19 others like this.
  5. Lord_Alfred

    Lord_Alfred Client

    Joined:
    Oct 9, 2015
    Messages:
    2,884
    Likes Received:
    2,472
    Однозначно мой голос будет отдан тебе :-)
    Даже захотелось переписать через методы расширений часть кода, а за Sentry отдельный жирный плюс - юзал его ранее под python)
     
    Last edited: May 10, 2018
    Juniorcpa and shtift like this.
  6. Advert31337

    Advert31337 Client

    Joined:
    Dec 18, 2016
    Messages:
    37
    Likes Received:
    27
    Прямо пачка годноты!!!
     
  7. mux76

    mux76 Client

    Joined:
    Dec 13, 2010
    Messages:
    260
    Likes Received:
    117
    Круто. Спасибо.
    PS После последних событий, мне кажется, что к окончанию голосования библиотека ZennoExtensions здесь будет уже прикреплена)))
     
  8. Advert31337

    Advert31337 Client

    Joined:
    Dec 18, 2016
    Messages:
    37
    Likes Received:
    27
    Мне кажется, что появится общая ветка на форуме для СиШарперов, которая будет писать моды и библиотеки по Зенку, как на тотже питон.
     
  9. Zymlex

    Zymlex Client

    Joined:
    Oct 24, 2016
    Messages:
    1,475
    Likes Received:
    754
    Уже давно есть раздел "Снипеты", как раз для этого.
     
  10. texnorip

    texnorip Client

    Joined:
    Oct 22, 2016
    Messages:
    12
    Likes Received:
    7
    Очень годный материал,тоже отдам голос.
    p/s
    несколько недель назад обратил внимание на https://yandex.ru/search/?text=ZennoExtensions&&lr=10472 , библиотека находится в свободном доступе на разных ресурсах,видимо это мотивировало автора написать статью.
     
  11. List

    List Client

    Joined:
    Sep 5, 2013
    Messages:
    29
    Likes Received:
    25
    @shtift ты лучший :-) развиваешь народ! Голосую за тебя.
     
    shtift likes this.
  12. SadisT_UA

    SadisT_UA Client

    Joined:
    Dec 14, 2012
    Messages:
    35
    Likes Received:
    6
    Познавательная тема, спасибо. Голос за тебя.
     
    shtift likes this.
  13. Max

    Max Client

    Joined:
    Jun 17, 2012
    Messages:
    129
    Likes Received:
    15
    Нормальная тема для опенсор проекта и не более... По хорошему либу можно было запилить на гитхаб и поставлять с новой версией зенки... Те либа полностью перешла под патронаж разрабов зенки... Где пользователи могли сами впиливать общий функционал без требования модификации основной кодовой базы зенки, а разработчики зенки могли бы сосредоточиться больше на сложных вещах. ИМХО.

    Ну а так да норм статья, правда ничего не стандартного тут нет. Ну и да - пока это топ)
     
  14. AZANIR

    AZANIR Client

    Joined:
    Jun 9, 2014
    Messages:
    267
    Likes Received:
    107
    однозначно тоже буду голосовать за тебя , пока это лучшее что было из статей.
     
    shtift likes this.
  15. SergSh

    SergSh Client

    Joined:
    May 10, 2017
    Messages:
    351
    Likes Received:
    223
    Как в гугл таблици передавать данные?
     
  16. radv

    radv Client

    Joined:
    May 11, 2015
    Messages:
    254
    Likes Received:
    129
    Я еще не все из Швейцарского ножа начал применять, а тут еще столько интересного :bp:
     
    shtift likes this.
  17. vrska

    vrska Client

    Joined:
    Feb 7, 2010
    Messages:
    251
    Likes Received:
    91
    Find неправильный глагол. Found, а не finded. Тогда уж loaded, мы же ждем когда элемент загрузится
    Статья с гугл таблицами интересная
     
    shtift likes this.
  18. novichok

    novichok Client

    Joined:
    Apr 17, 2016
    Messages:
    167
    Likes Received:
    49
    Вот это да. Как же я далек от этого. Не хватает мне усердия и усидчивости всё это изучить(
    +
     
  19. yriy158

    yriy158 Client

    Joined:
    Aug 10, 2013
    Messages:
    401
    Likes Received:
    230
    Очень годно! И хотя я большую часть о C# не понял, так как только изуча азы, но уже могу представить что полезность зашкаливает. За гугл таблицы огромный плюс, однозначно применять буду!
     
    shtift likes this.
  20. SergSh

    SergSh Client

    Joined:
    May 10, 2017
    Messages:
    351
    Likes Received:
    223
    Покажи вызов как передать в гугл таблици данные с этим решением.
     
  21. intagens

    intagens Client

    Joined:
    Sep 28, 2015
    Messages:
    70
    Likes Received:
    4
    shtift, я уже который день борюсь с отправкой данных в гугл таблицы ... можешь подсказать где ошибка?
    для получения данных, при отправке в ГЕТ запросе Url в таком виде
    Code (text):
    1. https://sheets.googleapis.com/v4/spreadsheets/spreadsheetId/values/Sheet1!A1:E1?majorDimension=ROWS&key={YOUR_API_KEY}
    проект отрабатывает отлично
    а если я отправляю данные POST запросом с вот таким Url
    Code (text):
    1. https://sheets.googleapis.com/v4/spreadsheets/spreadsheetId/values/Sheet1!A1:E1:append?valueInputOption=USER_ENTERED&key={YOUR_API_KEY}
    в ответе получаю ошибку
    {
    "error": {
    "code": 401,
    "message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
    "status": "UNAUTHENTICATED"
    }
    }
     
  22. shtift

    shtift Client

    Joined:
    Jul 29, 2015
    Messages:
    149
    Likes Received:
    239
    Не знаю. Я не изобретал свой велосипед и делал все через Google.Apis.Sheets.v4 .
     
  23. stud

    stud Client

    Joined:
    Jun 23, 2013
    Messages:
    25
    Likes Received:
    22
    Подскажи, как это подключить в ZP
    в студии все работает, в постер добавляю все библиотеки, ошибка
    Компиляция кода проекта Ошибка при компиляции общего кода "CS0234" "Имя типа или пространства имен "Services" отсутствует в пространстве имен "Google.Apis" (пропущена ссылка на сборку?)". [Строка: -12; Cтолбец: 19]

    Вроде все ссылки прописаны.
    [​IMG]

    [​IMG]
     
  24. stud

    stud Client

    Joined:
    Jun 23, 2013
    Messages:
    25
    Likes Received:
    22
    @shtift
    можешь проект для постера выложить, для использования Google-таблиц ?
     
  25. shtift

    shtift Client

    Joined:
    Jul 29, 2015
    Messages:
    149
    Likes Received:
    239
    Не знаю почему не получается подключить напрямую. Опишите логику работы с таблицей в отдельной сборке и уже её подключайте в зенку.
     
  26. nick3711

    nick3711 Client

    Joined:
    Apr 14, 2018
    Messages:
    127
    Likes Received:
    15
    Первый раз буду голосовать, где галку ставить???? Мой голос за тебя.
     
  27. shtift

    shtift Client

    Joined:
    Jul 29, 2015
    Messages:
    149
    Likes Received:
    239
    http://zennolab.com/discussion/threads/golosovanie.48764/

    Голосовать могут только пользователи со статусом Client.
     
  28. nick3711

    nick3711 Client

    Joined:
    Apr 14, 2018
    Messages:
    127
    Likes Received:
    15
  29. AZANIR

    AZANIR Client

    Joined:
    Jun 9, 2014
    Messages:
    267
    Likes Received:
    107
    Дык толку, ты статус не получил на форуме.
     
  30. nick3711

    nick3711 Client

    Joined:
    Apr 14, 2018
    Messages:
    127
    Likes Received:
    15
    Как его получить, у меня по всей видимости сейчас статус "ВСЁ СЛОЖНО":dy:*HAHA*
     

Пользователи просматривающие тему (Пользователей: 0, Гостей: 0)