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

shtift

Client
Регистрация
29.07.2015
Сообщения
148
Реакции
291
Баллы
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 новый метод. Лучше для каждого расширяемого типа создавать отдельный класс, как на скрине. В статье я буду писать все одном классе для удобства.
z9OvpCN.png

Метод будет принимать 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();
}

В видео рассматривается как применение методов расширения может выглядеть на практике.

 
Номер конкурса статей
  1. Девятый конкурс статей
Тема статьи
  1. Нестандартные хаки
Последнее редактирование:
Облачные сервисы логирования

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

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

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


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

Все довольно просто. Устанавливаем библиотеку из Nuget и создаем экземпляр класса RavenClient в конструктор которого передаем сгенерированную ссылку нашего проекта.
C#:
Развернуть Свернуть Копировать
var ravenClient = new RavenClient("https://2a485ba182874f15b4ef5ce2b53e42db:acce7ab5162848558b8e55a680f39bc8@sentry.io/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 раз в час и т.д. Более подробно можно почитать в официальной документации.
 
Последнее редактирование:
Использование 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
 
Последнее редактирование:
Конкурсные статьи ограничены тремя темами на одного человека, но есть еще одна тема, которую я рассматриваю во внеконкурсных статьях это "Разработка шаблонов через VisualStudio". Рекомендую к ознакомлению.
 
Последнее редактирование:
Однозначно мой голос будет отдан тебе :-)
Даже захотелось переписать через методы расширений часть кода, а за Sentry отдельный жирный плюс - юзал его ранее под python)
 
Последнее редактирование:
  • Спасибо
Реакции: Juniorcpa и shtift
Круто. Спасибо.
PS После последних событий, мне кажется, что к окончанию голосования библиотека ZennoExtensions здесь будет уже прикреплена)))
 
Круто. Спасибо.
PS После последних событий, мне кажется, что к окончанию голосования библиотека ZennoExtensions здесь будет уже прикреплена)))
Мне кажется, что появится общая ветка на форуме для СиШарперов, которая будет писать моды и библиотеки по Зенку, как на тотже питон.
 
Очень годный материал,тоже отдам голос.
p/s
несколько недель назад обратил внимание на https://yandex.ru/search/?text=ZennoExtensions&&lr=10472 , библиотека находится в свободном доступе на разных ресурсах,видимо это мотивировало автора написать статью.
 
@shtift ты лучший :-) развиваешь народ! Голосую за тебя.
 
  • Спасибо
Реакции: shtift
Познавательная тема, спасибо. Голос за тебя.
 
  • Спасибо
Реакции: shtift
Очень годный материал,тоже отдам голос.
p/s
несколько недель назад обратил внимание на https://yandex.ru/search/?text=ZennoExtensions&&lr=10472 , библиотека находится в свободном доступе на разных ресурсах,видимо это мотивировало автора написать статью.

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

Ну а так да норм статья, правда ничего не стандартного тут нет. Ну и да - пока это топ)
 
однозначно тоже буду голосовать за тебя , пока это лучшее что было из статей.
 
  • Спасибо
Реакции: shtift
  • Спасибо
Реакции: shtift
Find неправильный глагол. Found, а не finded. Тогда уж loaded, мы же ждем когда элемент загрузится
Статья с гугл таблицами интересная
 
  • Спасибо
Реакции: VerBin и shtift
Вот это да. Как же я далек от этого. Не хватает мне усердия и усидчивости всё это изучить(
+
 
Очень годно! И хотя я большую часть о C# не понял, так как только изуча азы, но уже могу представить что полезность зашкаливает. За гугл таблицы огромный плюс, однозначно применять буду!
 
  • Спасибо
Реакции: shtift
Очень годно! И хотя я большую часть о C# не понял, так как только изуча азы, но уже могу представить что полезность зашкаливает. За гугл таблицы огромный плюс, однозначно применять буду!
Покажи вызов как передать в гугл таблици данные с этим решением.
 
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"
}
}
 
Не знаю. Я не изобретал свой велосипед и делал все через Google.Apis.Sheets.v4 .
Подскажи, как это подключить в ZP
в студии все работает, в постер добавляю все библиотеки, ошибка
Компиляция кода проекта Ошибка при компиляции общего кода "CS0234" "Имя типа или пространства имен "Services" отсутствует в пространстве имен "Google.Apis" (пропущена ссылка на сборку?)". [Строка: -12; Cтолбец: 19]

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


RZH8MD5.png
 
Подскажи, как это подключить в ZP
в студии все работает, в постер добавляю все библиотеки, ошибка
Не знаю почему не получается подключить напрямую. Опишите логику работы с таблицей в отдельной сборке и уже её подключайте в зенку.
 

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