Найти тему
ZZnOB.Ru

Использование Google Drive в приложении C #

Оглавление

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

Необходимые условия

  • включен  Google Drive API в консоли Google Cloud;
  • зарегистрирован проект;
  • создан клиент OAuth 2.0 с разрешениями API-интерфейса Google drive;
  • установлен пакет nuget для Google drive v3 https://www.nuget.org/packages/Google.Apis.Drive.v3/ .

Начнем с создания пустого класса

public class DriveApiService
{

}

Аутентификация

У нас есть 2 варианта, чтобы получить доступ к хранилищу диска

  • учетная запись службы
  • учетная запись пользователя

Если вы не хотите, чтобы пользовательское взаимодействие и авторизация использовали учетную запись службы, у этой учетной записи есть недостатки: вы не можете получить доступ к интерфейсу Google Drive для этой учетной записи и не можете купить дополнительное хранилище для этой учетной записи.

Второй вариант — использовать наш личный аккаунт, но для этого потребуется однократный ручной вход пользователя. Мы будем использовать второй вариант. Поэтому в конструкторе сервиса мы будем обрабатывать авторизацию с помощью файла client_id.json, который генерируется для клиента Google OAuth.

public class DriveApiService
{
protected static string[] scopes = { DriveService.Scope.Drive };
protected readonly UserCredential credential;
static string ApplicationName = "Название приложения";
protected readonly DriveService service;
protected readonly FileExtensionContentTypeProvider fileExtensionProvider;

public DriveApiService()
{
using (var stream =
new FileStream("client_id.json", FileMode.Open, FileAccess.Read))
{
string credPath = "token.json";

credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
scopes,
ВАШ_АДРЕС_ЭЛ_ПОЧТЫ, // Используйте константу или читайте из файла настроек
CancellationToken.None,
new FileDataStore(credPath, true)).Result;

fileExtensionProvider = new FileExtensionContentTypeProvider();
}

service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
}
}

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

services.AddSingleton<DriveApiService>();

После того, как вы запустите приложение, откроется окно браузера и вам потребуется войти в свою учетную запись Google. Если вход прошел успешно, в вашем приложении будет создан постоянный файл token.json, который будет содержать токен доступа и обновления для доступа к Google Drive API . Вам не нужно будет входить в систему до тех пор, пока созданный файл лежит рядом с приложением, а ваш токен обновления действителен. Токен без ограничения по времени, но если вы измените свой пароль учетной записи Google, он будет отозван, и вам придется снова войти в систему вручную.

Список файлов

Диск Google не имеет структуры каталогов (вы не можете использовать путь для навигации «root / parent / child.type»), он использует родительские дочерние отношения, один дочерний элемент может быть в нескольких родителях. В нашем приложении мы храним эти отношения в БД и эмулируем классическое поведение каталога со всеми его ограничениями, и мы фактически используем пути, но эта статья не будет охватывать эту часть.

Чтобы получить файлы в каталоге, нам нужно знать его идентификатор. Мы можем получить доступ к корневому каталогу с помощью ключевого слова root.

public IList<Google.Apis.Drive.v3.Data.File> ListEntities(string id = "root")
{
FilesResource.ListRequest listRequest = service.Files.List();
listRequest.PageSize = 100;
listRequest.Fields = "nextPageToken, files(id, name, parents, createdTime, modifiedTime, mimeType)";
listRequest.Q = $"'{id}' in parents";

return listRequest.Execute().Files;
}

Метод ListEntities возвращает список файлов со всеми определенными полями, которые находятся в каталоге с заданным идентификатором. Если идентификатор не установлен, мы используем корневое ключевое слово по умолчанию. Новая папка должна иметь имя, тип mime и определенный родительский элемент. Будьте осторожны, Google диск разрешает несколько файлов / папок с одним и тем же именем (мы обрабатываем это на уровне базы данных приложения, так что мы заботимся :))

Создание папок

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

public Google.Apis.Drive.v3.Data.File CreateFolder(string name,string id = "root")
{
var fileMetadata = new Google.Apis.Drive.v3.Data.File()
{
Name = name,
MimeType = "application/vnd.google-apps.folder",
Parents = new[] { id }
};

var request = service.Files.Create(fileMetadata);
request.Fields = "id, name, parents, createdTime, modifiedTime, mimeType";

return request;

Если указан идентификатор родителя, мы создаем папку в определенном родителе, если нет, то мы создаем папку в корне. Ответ будет содержать все определенные поля.

Загрузка файла

Загрузка файла аналогична созданию папки.

public async Task<Google.Apis.Drive.v3.Data.File> Upload(IFormFile file, string documentId)
{
var name = ($"{DateTime.UtcNow.ToString()}.{Path.GetExtension(file.FileName)}");
var mimeType = file.ContentType;

var fileMetadata = new Google.Apis.Drive.v3.Data.File()
{
Name = name,
MimeType = mimeType,
Parents = new[] { documentId }
};

FilesResource.CreateMediaUpload request;
using (var stream = file.OpenReadStream())
{
request = service.Files.Create(
fileMetadata, stream, mimeType);
request.Fields = "id, name, parents, createdTime, modifiedTime, mimeType, thumbnailLink";
await request.UploadAsync();
}


return request.ResponseBody;
}

Сначала мы генерируем новое уникальное имя файла для нашего файла с правильным расширением, мы извлекаем правильный тип MIME из нашего файла и заполняем метаданные нашего файла. Затем мы создаем запрос на загрузку и загружаем наш поток. Ответ будет содержать все определенные поля.

Переименование файлов / папок

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

public void Rename(string name, string id)
{
Google.Apis.Drive.v3.Data.File file = service.Files.Get(id).Execute();

var update = new Google.Apis.Drive.v3.Data.File();
update.Name = name;

service.Files.Update(update, id).Execute();
}

Сначала мы получаем наш файл по указанному идентификатору, затем создаем запрос на обновление с метаданными, которые мы хотим изменить. Если вы хотите, вы могли бы реализовать некоторую обработку ошибок, потому что, если файл с данным идентификатором не существует, то он выдает ошибку (у нас есть обработка ошибок на верхнем уровне).

Удаление файла / папки

Удаление сущностей является прямым.

public void Remove(string id)
{
service.Files.Delete(id).Execute();
}

Скачивание файла

Мы можем реализовать загрузку файла, которая загружает файл по его идентификатору, мы храним его содержимое в потоке памяти, устанавливаем его положение равным 0, чтобы мы могли работать с ним на верхних уровнях, а затем возвращаем поток.

public async Task<Stream> Download(string fileId)
{
Stream outputstream = new MemoryStream();
var request = service.Files.Get(fileId);

await request.DownloadAsync(outputstream);

outputstream.Position = 0;

return outputstream;
}

Бонус: мултискачивание — архивируем и качаем.

Сначала мы реализуем модель DTO, которая описывает наш файл.

public class ZipVM
{
public string Name { get; set; }
public string DriveId { get; set; }
}

Сначала мы реализуем модель DTO, которая описывает наш файл. Мы храним собственные имена файлов в нашей базе данных, на диске мы используем имена файлов на основе даты, чтобы иметь правильные имена файлов в сгенерированном zip-файле, поэтому нам нужно предоставить правильные имена файлов в метод, если вам это не нужно, вы можете использовать простой список идентификаторов или вы можете использовать метод Download.

Обработка загрузки zip-элемента аналогична загрузке одного файла, но этот метод работает с нашей моделью DTO и возвращает кортеж, состоящий из потока и имени файла. Зачем нам это нужно? На самом деле Google Drive API не предоставляет ничего для загрузки нескольких файлов одновременно.

public async Task<(Stream,string)> DownloadZipItem(ZipVM vm)
{
Stream outputstream = new MemoryStream();
var request = service.Files.Get(vm.DriveId);

await request.DownloadAsync(outputstream);

outputstream.Position = 0;

return (outputstream, vm.Name);
}

Метод Zip принимает список DTO, которые мы определили. Затем мы создаем список задач DownloadZipItem из предоставленных DTO, которые мы вызываем параллельно. Затем мы создаем архив и возвращаем его как байтовый массив.

public async Task<byte[]> Zip(List<ZipVM> documents)
{
List<Task<(Stream,string)>> downloadTasks = new List<Task<(Stream,string)>>();

for(int i = 0; i<documents.Count; i++)
{
downloadTasks.Add(DownloadZipItem(documents[i]));
}

await Task.WhenAll(downloadTasks);


List<(Stream, string)> files = downloadTasks.Select(t => t.Result).ToList();

byte[] archiveFile;
using (var archiveStream = new MemoryStream())
{
using (var archive = new ZipArchive(archiveStream, ZipArchiveMode.Create, true))
{
int i = 0;
foreach (var file in files)
{
var zipArchiveEntry = archive.CreateEntry(file.Item2, CompressionLevel.Fastest);
using (var zipStream = zipArchiveEntry.Open())
file.Item1.CopyTo(zipStream);
}
}

archiveFile = archiveStream.ToArray();
}

return archiveFile;
}

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

Не забудьте зайти на блог, там бывают полезные записи, которые я не публикую тут!

А чтобы быть в курсе последних моих постов, подписывайтесь на мой канал в Telegram.

Google
89,1 тыс интересуются