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

shtift

Client
Joined
Jul 29, 2015
Messages
148
Reaction score
290
Points
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.
Это основное приложение, предназначенное для выполнения автоматизированных шаблонов действий (ботов).
Подробнее...

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

Last edited:

shtift

Client
Joined
Jul 29, 2015
Messages
148
Reaction score
290
Points
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 раз в час и т.д. Более подробно можно почитать в официальной документации.
 
Last edited:

shtift

Client
Joined
Jul 29, 2015
Messages
148
Reaction score
290
Points
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
 
Last edited:

shtift

Client
Joined
Jul 29, 2015
Messages
148
Reaction score
290
Points
63
Конкурсные статьи ограничены тремя темами на одного человека, но есть еще одна тема, которую я рассматриваю во внеконкурсных статьях это "Разработка шаблонов через VisualStudio". Рекомендую к ознакомлению.
 
Last edited:

Lord_Alfred

Client
Joined
Oct 9, 2015
Messages
3,916
Reaction score
3,859
Points
113
Однозначно мой голос будет отдан тебе :-)
Даже захотелось переписать через методы расширений часть кода, а за Sentry отдельный жирный плюс - юзал его ранее под python)
 
Last edited:

Advert31337

Client
Joined
Dec 18, 2016
Messages
53
Reaction score
38
Points
18
Прямо пачка годноты!!!
 

mux76

Client
Joined
Dec 13, 2010
Messages
259
Reaction score
119
Points
43
Круто. Спасибо.
PS После последних событий, мне кажется, что к окончанию голосования библиотека ZennoExtensions здесь будет уже прикреплена)))
 

Advert31337

Client
Joined
Dec 18, 2016
Messages
53
Reaction score
38
Points
18
Круто. Спасибо.
PS После последних событий, мне кажется, что к окончанию голосования библиотека ZennoExtensions здесь будет уже прикреплена)))
Мне кажется, что появится общая ветка на форуме для СиШарперов, которая будет писать моды и библиотеки по Зенку, как на тотже питон.
 

Yuriy Zymlex

Moderator
Staff member
Joined
Oct 24, 2016
Messages
6,381
Reaction score
3,304
Points
113
Мне кажется, что появится общая ветка на форуме для СиШарперов, которая будет писать моды и библиотеки по Зенку, как на тотже питон.
Уже давно есть раздел "Снипеты", как раз для этого.
 

texnorip

Client
Joined
Oct 22, 2016
Messages
26
Reaction score
20
Points
3
Очень годный материал,тоже отдам голос.
p/s
несколько недель назад обратил внимание на https://yandex.ru/search/?text=ZennoExtensions&&lr=10472 , библиотека находится в свободном доступе на разных ресурсах,видимо это мотивировало автора написать статью.
 

List

Client
Joined
Sep 5, 2013
Messages
33
Reaction score
26
Points
18
@shtift ты лучший :-) развиваешь народ! Голосую за тебя.
 
  • Thank you
Reactions: shtift

SadisT_UA

Client
Joined
Dec 14, 2012
Messages
38
Reaction score
8
Points
8
Познавательная тема, спасибо. Голос за тебя.
 
  • Thank you
Reactions: shtift

Max

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

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

AZANIR

Client
Joined
Jun 9, 2014
Messages
405
Reaction score
196
Points
43
однозначно тоже буду голосовать за тебя , пока это лучшее что было из статей.
 
  • Thank you
Reactions: shtift

SergSh

Client
Joined
May 10, 2017
Messages
541
Reaction score
395
Points
63

radv

Client
Joined
May 11, 2015
Messages
3,693
Reaction score
1,871
Points
113
Ускоряем разработку с методами расширения
Я еще не все из Швейцарского ножа начал применять, а тут еще столько интересного :bp:
 
  • Thank you
Reactions: shtift

vrska

Client
Joined
Feb 7, 2010
Messages
589
Reaction score
408
Points
63
Find неправильный глагол. Found, а не finded. Тогда уж loaded, мы же ждем когда элемент загрузится
Статья с гугл таблицами интересная
 
  • Thank you
Reactions: VerBin and shtift

novichok

Client
Joined
Apr 17, 2016
Messages
173
Reaction score
63
Points
28
Вот это да. Как же я далек от этого. Не хватает мне усердия и усидчивости всё это изучить(
+
 

yriy158

Client
Joined
Aug 10, 2013
Messages
491
Reaction score
301
Points
63
Очень годно! И хотя я большую часть о C# не понял, так как только изуча азы, но уже могу представить что полезность зашкаливает. За гугл таблицы огромный плюс, однозначно применять буду!
 
  • Thank you
Reactions: shtift

SergSh

Client
Joined
May 10, 2017
Messages
541
Reaction score
395
Points
63
Очень годно! И хотя я большую часть о C# не понял, так как только изуча азы, но уже могу представить что полезность зашкаливает. За гугл таблицы огромный плюс, однозначно применять буду!
Покажи вызов как передать в гугл таблици данные с этим решением.
 

intagens

Client
Joined
Sep 28, 2015
Messages
207
Reaction score
31
Points
28
shtift, я уже который день борюсь с отправкой данных в гугл таблицы ... можешь подсказать где ошибка?
для получения данных, при отправке в ГЕТ запросе Url в таком виде
Code:
https://sheets.googleapis.com/v4/spreadsheets/spreadsheetId/values/Sheet1!A1:E1?majorDimension=ROWS&key={YOUR_API_KEY}
проект отрабатывает отлично
а если я отправляю данные POST запросом с вот таким Url
Code:
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
Joined
Jul 29, 2015
Messages
148
Reaction score
290
Points
63
можешь подсказать где ошибка?
Не знаю. Я не изобретал свой велосипед и делал все через Google.Apis.Sheets.v4 .
 

stud

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

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


 

stud

Client
Joined
Jun 23, 2013
Messages
88
Reaction score
138
Points
33
@shtift
можешь проект для постера выложить, для использования Google-таблиц ?
 

shtift

Client
Joined
Jul 29, 2015
Messages
148
Reaction score
290
Points
63
Подскажи, как это подключить в ZP
в студии все работает, в постер добавляю все библиотеки, ошибка
Не знаю почему не получается подключить напрямую. Опишите логику работы с таблицей в отдельной сборке и уже её подключайте в зенку.
 

nick3711

Client
Joined
Apr 14, 2018
Messages
138
Reaction score
17
Points
18
Первый раз буду голосовать, где галку ставить???? Мой голос за тебя.
 

shtift

Client
Joined
Jul 29, 2015
Messages
148
Reaction score
290
Points
63

nick3711

Client
Joined
Apr 14, 2018
Messages
138
Reaction score
17
Points
18

AZANIR

Client
Joined
Jun 9, 2014
Messages
405
Reaction score
196
Points
43

nick3711

Client
Joined
Apr 14, 2018
Messages
138
Reaction score
17
Points
18
Дык толку, ты статус не получил на форуме.
Как его получить, у меня по всей видимости сейчас статус "ВСЁ СЛОЖНО":dy:*HAHA*
 

Users Who Are Viewing This Thread (Total: 1, Members: 0, Guests: 1)