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

shtift

Client
Регистрация
29.07.2015
Сообщения
148
Благодарностей
290
Баллы
63
Сегодня я хочу поговорить о наработках в области разработки шаблонов, которые облегчают жизнь как на стадии разработки и отладки, так и на стадии поддержки уже готового шаблона.

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

В каждом шаблоне нам приходится выполнять одни и те же действия. Типичный алгоритм:
  1. Загрузить страницу;
  2. Подождать пока страница полностью прогрузится;
  3. Найти элемент;
  4. Проверить, что элемент был найден;
  5. Кликнуть/получить значение/вызвать событие/что-то еще;
  6. Перейти к шагу 1 или 3.
Обычно это выливается в простыни однотипного кода. Пример:
C#:
instance.ClearCache();
instance.ClearCookie();

var tab = instance.ActiveTab;
tab.Navigate("ya.ru");

if(tab.IsBusy)
{
     tab.WaitDownloading();
}

var searchInput = tab.FindElementByXPath("//input", 0);
if(searchInput.IsVoid)
{
     throw new Exception("Поле ввода запроса не найдено.");
}

searchInput.SetValue("test request", "full", false);

var findButton = tab.FindElementByXPath("//button" , 0);
if(findButton.IsVoid)
{
     throw new Exception("Кнопка \"Найти\" не найдена");
}

findButton.Click();

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

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

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

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

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

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

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

Начнем с этой части кода:
C#:
tab.Navigate("ya.ru");

if(tab.IsBusy)
{
     tab.WaitDownloading();
}
В шаблонах очень часто приходится использовать метод Navigate для загрузки страницы и я не помню ни одного случая, когда мне не требовалось бы сразу дождаться загрузки страницы. Так почему бы не вынести этот кусок кода в метод расширения типа Tab ?!

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

Fluent Interface

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

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

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

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

C#:
var searchInput = tab.FindElementByXPath("//input", 0);
if(searchInput.IsVoid)
{
     throw new Exception("Поле ввода запроса не найдено.");
}

//манипуляции с searchInput

var findButton = tab.FindElementByXPath("//button" , 0);
if(findButton.IsVoid)
{
     throw new Exception("Кнопка \"Найти\" не найдена");
}

//манипуляции с findButton

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

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

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

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

Не отходя от кассы добавляем метод расширения для типа HtmlElement, который будет бросать исключение, если элемент не найден:
C#:
public static HtmlElement ThrowIfNull(this HtmlElement he,
                                      string exceptionMessage = "HtmlElement не найден (IsVoid).")
{
    if (he.IsVoid)
    {
        throw new Exception(exceptionMessage);
    }
    return he;
}

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

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

Итого первоначальный код преобразился до такого:
C#:
instance.ClearCache();
instance.ClearCookie();

var tab = instance.ActiveTab;
tab.NavigateAndWait("ya.ru");

tab.GetElementByXpath("//input").ThrowIfNull("Поле ввода запроса не найдено.").SetValue("test request", "full", false);
tab.GetElementByXpath("//button").ThrowIfNull("Кнопка \"Найти\" не найдена").Click();
Такой код более читабельный, а главное его быстрее, проще и просто приятнее писать.
C#:
public static class ZennoExtensions
{
     public static Tab NavigateAndWait(this Tab tab, string url, string referer = "")
     {
         tab.Navigate(url, referer);
         if(tab.IsBusy)
         {
            tab.WaitDownloading();
         }
         return tab;
     }
    public static HtmlElement GetElementByXpath(this Tab tab, string xpath, int number = 0)
    {
         return tab.FindElementByXPath(xpath, number);
    }
    public static HtmlElement ThrowIfNull(this HtmlElement he, string exceptionMessage = "HtmlElement не найден (IsVoid).")
    {
        if (he.IsVoid)
        {
            throw new Exception(exceptionMessage);
        }
        return he;
    }
}

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

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

C#:
public static class ProjectExtensions
{
    public static IZennoPosterProjectModel Debug(this IZennoPosterProjectModel project, string message, bool showInPoster = false)
    {
        project.SendInfoToLog(message, showInPoster);
        return project;
    }
    public static IZennoPosterProjectModel Info(this IZennoPosterProjectModel project, string message, bool showInPoster = true)
    {
        project.SendInfoToLog(message, showInPoster);
        return project;
    }
    public static IZennoPosterProjectModel Warning(this IZennoPosterProjectModel project, string message, bool showInPoster = true)
    {
        project.SendWarningToLog(message, showInPoster);
        return project;
    }
    public static IZennoPosterProjectModel Error(this IZennoPosterProjectModel project, string message, bool showInPoster = true)
    {
        project.SendErrorToLog(message, showInPoster);
        return project;
    }
}

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

Можете также черпать идеи из документации к библиотеке ZennoExtensions.
Вот код метода WaitFor, который реализован в библиотеке:
C#:
/// <summary>
/// Выполняет ожидание, пока на странице не будет найден HTML элемент по указанному XPath, либо до истечения таймаута.
/// </summary>
/// <param name="tab"></param>
/// <param name="xpath">XPath для поиска.</param>
/// <param name="number">Номер совпадения.</param>
/// <param name="timeout">Длительность поиска в миллисекундах. По умолчанию 5000.</param>
/// <returns>Возвращает true, если элемент был найден.</returns>
public static bool WaitFor(this Tab tab, string xpath, int number = 0, int timeout = 5000)
{
    if (string.IsNullOrWhiteSpace(xpath))
    {
        throw new ArgumentException("XPath должен быть задан.");
    }

    for (var i = 0; i < timeout / 100; i++)
    {
        if (!tab.FindElementByXPath(xpath, number).IsVoid)
        {
            return true;
        }
        Thread.Sleep(100);
    }

    return false;
}

/// <summary>
/// Выполняет ожидание, пока на странице не будет найден HTML элемент по указанном атрибутам, либо до истечения
/// таймаута.
/// </summary>
/// <param name="tab"></param>
/// <param name="tags">Список тегов. Если количество тегов больше одного, их необходимо разделить ";".</param>
/// <param name="attrName">Имя атрибута.</param>
/// <param name="attrValue">Значение атрибута.</param>
/// <param name="searchKind">Тип поиска. Доступно: text, notext, regex.</param>
/// <param name="number">Номер совпадения.</param>
/// <param name="timeout">Длительность поиска в миллисекундах. По умолчанию 5000.</param>
/// <returns>Тот же объект <see cref="Tab"/>для Fluent Interface</returns>
public static Tab WaitFor(this Tab tab, string tags, string attrName, string attrValue, string searchKind,
    int number = 0, int timeout = 5000)
{
    for (var i = 0; i < timeout / 100; i++)
    {
        if (!tab.FindElementByAttribute(tags, attrName, attrValue, searchKind, number).IsVoid)
        {
            return tab;
        }
        Thread.Sleep(100);
    }

    return tab;
}

/// <summary>
/// Выполняет ожидание, пока предикат не вернет true, либо до истечения таймаута.
/// </summary>
/// <param name="tab"></param>
/// <param name="predicate">Условное выражение.</param>
/// <param name="timeout">Длительность проверки выражения в миллисекундах. По умолчанию 5000.</param>
/// <returns>Тот же объект <see cref="Tab"/>для Fluent Interface</returns>
public static Tab WaitFor(this Tab tab, Func<bool> predicate, int timeout = 5000)
{
    if (predicate == null)
    {
        throw new ArgumentNullException("predicate");
    }

    for (var i = 0; i < timeout / 100; i++)
    {
        if (predicate.Invoke())
        {
            return tab;
        }
        Thread.Sleep(100);
    }

    return tab;
}
Этот метод удобен в использовании при динамическом изменении страницы, когда нужно дождаться появления, исчезновения или изменения элемента. Первые две перегрузки метода WaitFor выполняют ожидание, пока не будет найден элемент, либо до истечения указанного времени. Третья перегрузка выполняет ожидание, пока переданный предикат не вернет true, либо до истечения указанного времени.

Пример использования:
C#:
var tab = instance.ActiveTab;
var isLoginButtonFinded = tab.WaitFor("//button[@id='login']", 0, 10000); //Ожидаем появления элемента на странице 10 секунд

if(!isLoginButtonFinded)
{
   tab.Refresh();
}
В видео рассматривается как применение методов расширения может выглядеть на практике.

 
Тема статьи
Нестандартные хаки
Номер конкурса статей
Девятый конкурс статей

Для запуска проектов требуется программа ZennoPoster или ZennoDroid.
Это основное приложение, предназначенное для выполнения автоматизированных шаблонов действий (ботов).
Подробнее...

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

Последнее редактирование:

shtift

Client
Регистрация
29.07.2015
Сообщения
148
Благодарностей
290
Баллы
63
Облачные сервисы логирования

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

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

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


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

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

Используем следующим образом:
C#:
try
{
    int i2 = 0;
    int i = 10 / i2;
}
catch (Exception exception)
{
    ravenClient.Capture(new SentryEvent(exception));
}

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

J3iwjso[1].png

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

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

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


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

shtift

Client
Регистрация
29.07.2015
Сообщения
148
Благодарностей
290
Баллы
63
Использование 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 на следующее:
C#:
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.Sheets.v4;
using Google.Apis.Sheets.v4.Data;

namespace ConsoleApp1
{
    class Program
    {
        // If modifying these scopes, delete your previously saved credentials
        // at ~/.credentials/sheets.googleapis.com-dotnet-quickstart.json
        static string[] Scopes = { SheetsService.Scope.Spreadsheets };
        static string ApplicationName = "ZennoTemplate";
        static Random Random = new Random();

        static void Main(string[] args)
        {
            string spreadsheetId = "id вашей таблицы";
            string range = "Accounts!A2:D";

            var service = Authorize();
         
            var newData = GenerateData();
            AppendData(service, spreadsheetId, range, newData);

            var data = GetData(service, spreadsheetId, range);
            PrintValues(data);
            Console.Read();
        }

        private static SheetsService Authorize()
        {
            UserCredential credential;

            using (var stream = new FileStream("client_secret.json", FileMode.Open, FileAccess.Read))
            {
                string credPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
                credPath = Path.Combine(credPath, ".credentials/quickstart.json");
                credential = GoogleWebAuthorizationBroker
                    .AuthorizeAsync(GoogleClientSecrets.Load(stream).Secrets, Scopes, "user", CancellationToken.None).Result;
                Console.WriteLine("Credential file saved to: " + credPath + Environment.NewLine);
            }

            // Create Google Sheets API service.
            var service =
                new SheetsService(new BaseClientService.Initializer()
                {
                    HttpClientInitializer = credential,
                    ApplicationName = ApplicationName,
                });
            return service;
        }

        public static IList<IList<object>> GetData(SheetsService service, string spreadsheetId, string range)
        {
            var request = service.Spreadsheets.Values.Get(spreadsheetId, range);

            var response = request.Execute();
            return response.Values;
        }

        public static void AppendData(SheetsService service, string spreadsheetId, string range, IList<IList<object>> values)
        {
            var body = new ValueRange{Values = values};

            var request = service.Spreadsheets.Values.Append(body, spreadsheetId, range);
            request.InsertDataOption = SpreadsheetsResource.ValuesResource.AppendRequest.InsertDataOptionEnum.INSERTROWS;
            request.ValueInputOption = SpreadsheetsResource.ValuesResource.AppendRequest.ValueInputOptionEnum.RAW;
            request.Execute();
        }

        public static void PrintValues(IList<IList<object>> values)
        {
            if (values == null || values.Count == 0)
                return;

            foreach (var row in values)
            {
                Console.WriteLine("Сайт: {0} Дата добавления: {1} Аккаунт: {2}:{3}", row[0], row[1], row[2], row[3]);
            }
        }

        private static List<IList<object>> GenerateData()
        {
            var newData = new List<IList<object>>()
            {
                new List<object> {"mail.ru", DateTime.Now.ToString("g"), $"login{Random.Next(0, 999)}", "password"},
                new List<object> {"mail.ru", DateTime.Now.ToString("g"), $"login{Random.Next(0, 999)}", "password"},
                new List<object> {"mail.ru", DateTime.Now.ToString("g"), $"login{Random.Next(0, 999)}", "password"}
            };
            return newData;
        }

    }
}
11. Теперь создадим таблицу аккаунтов с которой будем работать.
uKGZxFl[1].png

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

13. Запустите проект. После выполнения в таблицу добавятся новые данные.
jHArZoY[1].png
 
Последнее редактирование:

shtift

Client
Регистрация
29.07.2015
Сообщения
148
Благодарностей
290
Баллы
63
Конкурсные статьи ограничены тремя темами на одного человека, но есть еще одна тема, которую я рассматриваю во внеконкурсных статьях это "Разработка шаблонов через VisualStudio". Рекомендую к ознакомлению.
 
Последнее редактирование:

Lord_Alfred

Client
Регистрация
09.10.2015
Сообщения
3 916
Благодарностей
3 852
Баллы
113
Однозначно мой голос будет отдан тебе :-)
Даже захотелось переписать через методы расширений часть кода, а за Sentry отдельный жирный плюс - юзал его ранее под python)
 
Последнее редактирование:
  • Спасибо
Реакции: Juniorcpa и shtift

Advert31337

Client
Регистрация
18.12.2016
Сообщения
53
Благодарностей
38
Баллы
18
Прямо пачка годноты!!!
 

mux76

Client
Регистрация
13.12.2010
Сообщения
259
Благодарностей
119
Баллы
43
Круто. Спасибо.
PS После последних событий, мне кажется, что к окончанию голосования библиотека ZennoExtensions здесь будет уже прикреплена)))
 

Advert31337

Client
Регистрация
18.12.2016
Сообщения
53
Благодарностей
38
Баллы
18
Круто. Спасибо.
PS После последних событий, мне кажется, что к окончанию голосования библиотека ZennoExtensions здесь будет уже прикреплена)))
Мне кажется, что появится общая ветка на форуме для СиШарперов, которая будет писать моды и библиотеки по Зенку, как на тотже питон.
 

Yuriy Zymlex

Moderator
Команда форума
Регистрация
24.10.2016
Сообщения
6 352
Благодарностей
3 279
Баллы
113

texnorip

Client
Регистрация
22.10.2016
Сообщения
26
Благодарностей
20
Баллы
3
Очень годный материал,тоже отдам голос.
p/s
несколько недель назад обратил внимание на https://yandex.ru/search/?text=ZennoExtensions&&lr=10472 , библиотека находится в свободном доступе на разных ресурсах,видимо это мотивировало автора написать статью.
 

List

Client
Регистрация
05.09.2013
Сообщения
33
Благодарностей
26
Баллы
18
@shtift ты лучший :-) развиваешь народ! Голосую за тебя.
 
  • Спасибо
Реакции: shtift

SadisT_UA

Client
Регистрация
14.12.2012
Сообщения
38
Благодарностей
8
Баллы
8
Познавательная тема, спасибо. Голос за тебя.
 
  • Спасибо
Реакции: shtift

Max

Client
Регистрация
17.06.2012
Сообщения
168
Благодарностей
44
Баллы
28
Очень годный материал,тоже отдам голос.
p/s
несколько недель назад обратил внимание на https://yandex.ru/search/?text=ZennoExtensions&&lr=10472 , библиотека находится в свободном доступе на разных ресурсах,видимо это мотивировало автора написать статью.
Нормальная тема для опенсор проекта и не более... По хорошему либу можно было запилить на гитхаб и поставлять с новой версией зенки... Те либа полностью перешла под патронаж разрабов зенки... Где пользователи могли сами впиливать общий функционал без требования модификации основной кодовой базы зенки, а разработчики зенки могли бы сосредоточиться больше на сложных вещах. ИМХО.

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

AZANIR

Client
Регистрация
09.06.2014
Сообщения
405
Благодарностей
196
Баллы
43
однозначно тоже буду голосовать за тебя , пока это лучшее что было из статей.
 
  • Спасибо
Реакции: shtift

SergSh

Client
Регистрация
10.05.2017
Сообщения
541
Благодарностей
395
Баллы
63

radv

Client
Регистрация
11.05.2015
Сообщения
3 673
Благодарностей
1 855
Баллы
113
  • Спасибо
Реакции: shtift

vrska

Client
Регистрация
07.02.2010
Сообщения
589
Благодарностей
406
Баллы
63
Find неправильный глагол. Found, а не finded. Тогда уж loaded, мы же ждем когда элемент загрузится
Статья с гугл таблицами интересная
 
  • Спасибо
Реакции: VerBin и shtift

novichok

Client
Регистрация
17.04.2016
Сообщения
173
Благодарностей
62
Баллы
28
Вот это да. Как же я далек от этого. Не хватает мне усердия и усидчивости всё это изучить(
+
 

yriy158

Client
Регистрация
10.08.2013
Сообщения
491
Благодарностей
300
Баллы
63
Очень годно! И хотя я большую часть о C# не понял, так как только изуча азы, но уже могу представить что полезность зашкаливает. За гугл таблицы огромный плюс, однозначно применять буду!
 
  • Спасибо
Реакции: shtift

SergSh

Client
Регистрация
10.05.2017
Сообщения
541
Благодарностей
395
Баллы
63
Очень годно! И хотя я большую часть о C# не понял, так как только изуча азы, но уже могу представить что полезность зашкаливает. За гугл таблицы огромный плюс, однозначно применять буду!
Покажи вызов как передать в гугл таблици данные с этим решением.
 

intagens

Client
Регистрация
28.09.2015
Сообщения
207
Благодарностей
31
Баллы
28
shtift, я уже который день борюсь с отправкой данных в гугл таблицы ... можешь подсказать где ошибка?
для получения данных, при отправке в ГЕТ запросе Url в таком виде
Код:
https://sheets.googleapis.com/v4/spreadsheets/spreadsheetId/values/Sheet1!A1:E1?majorDimension=ROWS&key={YOUR_API_KEY}
проект отрабатывает отлично
а если я отправляю данные POST запросом с вот таким Url
Код:
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"
}
}
 

shtift

Client
Регистрация
29.07.2015
Сообщения
148
Благодарностей
290
Баллы
63

stud

Client
Регистрация
23.06.2013
Сообщения
88
Благодарностей
138
Баллы
33
Не знаю. Я не изобретал свой велосипед и делал все через Google.Apis.Sheets.v4 .
Подскажи, как это подключить в ZP
в студии все работает, в постер добавляю все библиотеки, ошибка
Компиляция кода проекта Ошибка при компиляции общего кода "CS0234" "Имя типа или пространства имен "Services" отсутствует в пространстве имен "Google.Apis" (пропущена ссылка на сборку?)". [Строка: -12; Cтолбец: 19]

Вроде все ссылки прописаны.


 

stud

Client
Регистрация
23.06.2013
Сообщения
88
Благодарностей
138
Баллы
33
@shtift
можешь проект для постера выложить, для использования Google-таблиц ?
 

shtift

Client
Регистрация
29.07.2015
Сообщения
148
Благодарностей
290
Баллы
63
Подскажи, как это подключить в ZP
в студии все работает, в постер добавляю все библиотеки, ошибка
Не знаю почему не получается подключить напрямую. Опишите логику работы с таблицей в отдельной сборке и уже её подключайте в зенку.
 

nick3711

Client
Регистрация
14.04.2018
Сообщения
138
Благодарностей
17
Баллы
18
Первый раз буду голосовать, где галку ставить???? Мой голос за тебя.
 

shtift

Client
Регистрация
29.07.2015
Сообщения
148
Благодарностей
290
Баллы
63

nick3711

Client
Регистрация
14.04.2018
Сообщения
138
Благодарностей
17
Баллы
18

AZANIR

Client
Регистрация
09.06.2014
Сообщения
405
Благодарностей
196
Баллы
43

nick3711

Client
Регистрация
14.04.2018
Сообщения
138
Благодарностей
17
Баллы
18

Кто просматривает тему: (Всего: 1, Пользователи: 0, Гости: 1)