Допустим, хранилище является необходимой частью нашего приложения, но наш хостинг имеет ограниченный размер хранилища, и нам действительно нужно несколько гигабайт хранилища как можно дешевле. Итак, давайте создадим сервис, который сможет получать доступ и работать с Google Drive.
Необходимые условия
- включен Google Drive API в консоли Google Cloud;
- зарегистрирован проект;
- создан клиент OAuth 2.0 с разрешениями API-интерфейса Google drive;
Начнем с создания пустого класса
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.