C# Google Drive APIv3 Upload File - c#

I'm making a simple Application that Links to a Google Drive Account and then can Upload Files to any Directory and respond with a (direct) download Link.
I already got my User Credentials and DriveService objects, but I can't seem to find any good examples or Docs. on the APIv3.
As I'm not very familiar with OAuth, I'm asking for a nice and clear explanation on how to Upload a File with byte[] content now.
My Code for Linking the Application to a Google Drive Account: (Not sure if this works perfectly)
UserCredential credential;
string dir = Directory.GetCurrentDirectory();
string path = Path.Combine(dir, "credentials.json");
File.WriteAllBytes(path, Properties.Resources.GDJSON);
using(var stream = new FileStream(path, FileMode.Open, FileAccess.Read)) {
string credPath = Path.Combine(dir, "privatecredentials.json");
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
}
// Create Drive API service.
_service = new DriveService(new BaseClientService.Initializer() {
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
File.Delete(path);
My Code for Uploading so far: (Does not work obviously)
public void Upload(string name, byte[] content) {
Google.Apis.Drive.v3.Data.File body = new Google.Apis.Drive.v3.Data.File();
body.Name = name;
body.Description = "My description";
body.MimeType = GetMimeType(name);
body.Parents = new List() { new ParentReference() { Id = _parent } };
System.IO.MemoryStream stream = new System.IO.MemoryStream(content);
try {
FilesResource.InsertMediaUpload request = _service.Files.Insert(body, stream, GetMimeType(_uploadFile));
request.Upload();
return request.ResponseBody;
} catch(Exception) { }
}
Thanks!

Once you have enabled your Drive API, registered your project and obtained your credentials from the Developer Consol, you can use the following code for recieving the user's consent and obtaining an authenticated Drive Service
string[] scopes = new string[] { DriveService.Scope.Drive,
DriveService.Scope.DriveFile};
var clientId = "xxxxxx"; // From https://console.developers.google.com
var clientSecret = "xxxxxxx"; // From https://console.developers.google.com
// here is where we Request the user to give us access, or use the Refresh Token that was previously stored in %AppData%
var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets { ClientId = clientId,
ClientSecret = clientSecret},
scopes,
Environment.UserName,
CancellationToken.None,
new FileDataStore("MyAppsToken")).Result;
//Once consent is recieved, your token will be stored locally on the AppData directory, so that next time you wont be prompted for consent.
DriveService service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "MyAppName",
});
service.HttpClient.Timeout = TimeSpan.FromMinutes(100);
//Long Operations like file uploads might timeout. 100 is just precautionary value, can be set to any reasonable value depending on what you use your service for.
Following is a working piece of code for uploading to Drive.
// _service: Valid, authenticated Drive service
// _uploadFile: Full path to the file to upload
// _parent: ID of the parent directory to which the file should be uploaded
public static Google.Apis.Drive.v2.Data.File uploadFile(DriveService _service, string _uploadFile, string _parent, string _descrp = "Uploaded with .NET!")
{
if (System.IO.File.Exists(_uploadFile))
{
File body = new File();
body.Title = System.IO.Path.GetFileName(_uploadFile);
body.Description = _descrp;
body.MimeType = GetMimeType(_uploadFile);
body.Parents = new List<ParentReference>() { new ParentReference() { Id = _parent } };
byte[] byteArray = System.IO.File.ReadAllBytes(_uploadFile);
System.IO.MemoryStream stream = new System.IO.MemoryStream(byteArray);
try
{
FilesResource.InsertMediaUpload request = _service.Files.Insert(body, stream, GetMimeType(_uploadFile));
request.Upload();
return request.ResponseBody;
}
catch(Exception e)
{
MessageBox.Show(e.Message,"Error Occured");
}
}
else
{
MessageBox.Show("The file does not exist.","404");
}
}
Here's the little function for determining the MimeType:
private static string GetMimeType(string fileName)
{
string mimeType = "application/unknown";
string ext = System.IO.Path.GetExtension(fileName).ToLower();
Microsoft.Win32.RegistryKey regKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(ext);
if (regKey != null && regKey.GetValue("Content Type") != null)
mimeType = regKey.GetValue("Content Type").ToString();
return mimeType;
}
Additionally, you can register for the ProgressChanged event and get the upload status.
request.ProgressChanged += UploadProgessEvent;
request.ChunkSize = FilesResource.InsertMediaUpload.MinimumChunkSize; // Minimum ChunkSize allowed by Google is 256*1024 bytes. ie 256KB.
And
private void UploadProgessEvent(Google.Apis.Upload.IUploadProgress obj)
{
label1.Text = ((obj.ByteSent*100)/TotalSize).ToString() + "%";
// do updation stuff
}
That's pretty much it on Uploading..
Source.

If you've followed Google Drive API's .NET Quickstart guide, then you probably remember during first launch, a web page from google drive was prompting for authorization grant to access google drive with "Read only" permission?
The default scope "DriveService.Scope.DriveReadonly" from the quickstart guide can't be used if you intend on uploading files.
This worked for me
Remove "Drive ProtoType" from Apps connected to your account
Create another set of credentials with a new application name eg "Drive API .NET Quickstart2" in API Manager
Request access with this scope "DriveService.Scope.DriveFile"
private static readonly string[] Scopes = { DriveService.Scope.DriveReadonly };
private static readonly string ApplicationName = "Drive API .NET Quickstart2";}
You should land on a new page from google drive requesting new grant
Drive Prototype would like to: View and manage Google Drive files and folders that you have opened or created with this app
After allowing access, your application should be able to upload.

i have the same problem on mine application winforms c# fw 4.0
i installed already google drive api v3 by nuget
and also created json file from googles api and inserted into project
request.ResponseBody == null???
anyone has a solution for it ?
thanks by advance

I think you're going in the right direction, just a bit unsure.
The main steps in using the Google Drive API for a C# (.NET) application are
Enable the Google Drive API in your Google Account
Install the Google Drive SDK for .NET framework using "NuGet" package manager. For this, in Visual Studio, go to Tools -> NuGet Package Manager -> Package Manager Console and then enter the following command
Install-Package Google.Apis.Drive.v3
Make sure you "use" all the packages/libraries in your application using the "using" statements at the top. For example,
using Google.Apis.Auth.OAuth2;
using Google.Apis.Drive.v3;
using Google.Apis.Drive.v3.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;
The code you have written above seems correct to me (I have not hard tested it). But if you have trouble in uploading files with it, you can try different approaches by the links mentioned below.
The above steps are largely taken from Google Drive API's .NET Quickstart page.
Further, you can and should refer to Google's documentation for the Google Drive SDK for .NET framework.
I hope the above content helped you.

Related

Google Drive API and .NET Core - Create a copy of a file

I'm trying to use the Google Drive API to create a copy of a file and then use the Docs API to do a find and replace. I've been able to get it all working except the problem is the newly created file is owned by the service account that I created in my https://console.cloud.google.com/ account. Here is what I have:
internal class DriveHelper
{
public DriveService Service { get; set; }
const string APPLICATION_NAME = "sound-booth-scheduler";
static readonly string[] Scopes = { DriveService.Scope.Drive };
internal DriveHelper()
{
InitializeService();
}
private void InitializeService()
{
var credential = GetCredentialsFromFile();
Service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = APPLICATION_NAME
});
}
private GoogleCredential GetCredentialsFromFile()
{
GoogleCredential credential;
using var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read);
credential = GoogleCredential.FromStream(stream).CreateScoped(Scopes);
return credential;
}
}
DriveHelper driveHelper = new DriveHelper();
var templateFileRequest = driveHelper.Service.Files.Get("<file id>");
templateFileRequest.Fields = "owners, parents";
var templateFile = templateFileRequest.Execute();
var copyRequest = driveHelper.Service.Files.Copy(new File(), "<file id>");
copyRequest.Fields = "owners, parents, id";
var copiedFile = copyRequest.Execute();
The copy requests executes without any errors but the copiedFile has a parent of the service account, so I can't see it when I view my Google Drive in the browser. I've tried setting the parent using the following code, but it results in an error:
var updateRequest = driveHelper.Service.Files.Update(new File(), copiedFile.Id);
updateRequest.AddParents = templateFile.Parents.First();
updateRequest.RemoveParents = String.Join(",", copiedFile.Parents);
var updatedCopiedFile = updateRequest.Execute();
How do I make a copy of a file using the API and set my user account (the one who owns the service account) as the owner of the document?
My problem was that I was using a service account even though I don't have a G Suite account. I switched to using OAuth authentication and the file copy worked as expected. Here's the main parts of my code in case it might help someone else:
var secrets = GoogleClientSecrets.FromFile("client_secret_oath.json");
var userCredentials = GoogleWebAuthorizationBroker.AuthorizeAsync(
secrets.Secrets,
_Scopes,
"user",
CancellationToken.None,
new FileDataStore("token_send.json", true)
)
.Result;
var driveService = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = userCredentials,
ApplicationName = APPLICATION_NAME
});
//copy the file
var copyRequest = driveService.Files.Copy(new DriveData.File(), "<fileid>");
copyRequest.Fields = "id";
var copiedFile = copyRequest.Execute();
//rename the file
copiedFile = driveService.Files.Get(copiedFile.Id).Execute();
string fileId = copiedFile.Id;
copiedFile.Id = null;
copiedFile.Name = copiedFile.Name + "_Test";
var updateRequest = driveService.Files.Update(copiedFile, fileId);
var renamedFile = updateRequest.Execute();
Sharing and changing ownership of files
You would need to manually share the file with the Gmail or Workspace account.
Depending on which Version of Drive API you are using. You would need to use:
permissions:create
or
permissions:insert
This will allow you to create an option to directly share the file owned by the service account to another user with access to the Drive UI.
There is also a guide on how to utilize the "permission" for Drive V3 with a sample codes that you can implement.
You would need to make sure to review the steps on how the transfer of ownership works between Gmail and Workspace accounts due to the sharing restrictions that the organization might have, the key thing when implementing the sample code is to make sure the role=owner and transferOwnsership=true.
Reference
https://developers.google.com/drive/api/v2/reference/permissions/insert
https://developers.google.com/drive/api/v3/reference/permissions/create
Guide on how to manage sharing files: https://developers.google.com/drive/api/guides/manage-sharing#.net

Google Drive API - Search by file name only finds the file if it has recently been opened via Google Drive on chrome

I have a method that will search for a file by name and then download that file:
public static void DownloadFile(string filename)
{
service = GetDriveService();
//check if file exists and grab id
FilesResource.ListRequest listRequest = service.Files.List();
listRequest.SupportsAllDrives = true;
listRequest.IncludeItemsFromAllDrives = true;
listRequest.PageSize = 1000;
listRequest.Q = "name = '" + filename + ".pdf'";
FileList files = listRequest.Execute();
if (files.Files.Count > 0) //the file exists, DOWNLOAD
{
var request = service.Files.Get(files.Files[0].Id);
var stream = new System.IO.MemoryStream();
request.Download(stream);
}
}
The problem is that FileList files = listRequest.Execute(); will return 0 files unless I have recently opened that file on Google Drive in Chrome. it's like the files are not indexed. Am I missing a parameter?
I've tested this on loads of files and without fail, if I've opened the file on chrome FileList files = listRequest.Execute(); returns 1 element otherwise it returns nothing.
I have also tested searching in specific folders only, same issue.
Any help is much appreciated.
thanks
edit; Here is the GetDriveService Method:
private static DriveService GetDriveService()
{
string[] scopes = new string[] { DriveService.Scope.Drive }; // Full access
GoogleDrive cr = JsonConvert.DeserializeObject<GoogleDrive>(System.IO.File.ReadAllText(#"\\PATH_TO_JSONFILE\GoogleAPI.json"));
ServiceAccountCredential xCred = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(cr.client_email)
{
User = "xxxxx#xxxx.xx",
Scopes = new[] { DriveService.Scope.Drive }
}.FromPrivateKey(cr.private_key));
DriveService service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = xCred,
ApplicationName = "APPLICATION_NAME",
});
return service;
}
The User email is the same account I use to open the files in Chrome.
I have found the issue.
In GetDriveService() the User was set to my own email address and not to the service account address with account-wide delegation across our organisation.
I guess my email-address did not 'own' the files until I opened them once.
Hope this can prevent someone from doing the same mistake!
It seems this happens if you are defaulting to the "user" corpora and the files haven't been directly shared with you (i.e. you've been aliased in to having access instead).
In the case that you can't use a service account for what you're doing, you can fix this issue by changing the corpora parameter to "allDrives" instead.
So in your C# DownloadFile method just add
listRequest.Corpora="allDrives";

Google Drive API v3 use JSON instead of P12 (Service Account) - Unexpected character

Error: Unexpected character encountered while parsing value: e. Path
'', line 0, position 0.
I am using the Google .Net Client library to access the Google drive API v3 specifically the Google.Apis.Drive.v3 package. I am authorizing using "Service Account" with C#.
Authorization with the p12 key is no problem. However, JSON is recommended and p12 format is maintained for backward compatibility.
I downloaded the JSON file from the Google Developers Console and tried to make the authorization with the following code:
public static Google.Apis.Drive.v3.DriveService AuthenticateServiceAccountJSON(string keyFilePath) {
// check the file exists
if (!File.Exists(keyFilePath)) {
Console.WriteLine("An Error occurred - Key file does not exist");
return null;
}
string[] scopes = new string[] { DriveService.Scope.Drive, // view and manage your files and documents
DriveService.Scope.DriveAppdata, // view and manage its own configuration data
DriveService.Scope.DriveFile, // view and manage files created by this app
DriveService.Scope.DriveMetadataReadonly, // view metadata for files
DriveService.Scope.DriveReadonly, // view files and documents on your drive
DriveService.Scope.DriveScripts }; // modify your app scripts
try {
using (var stream = new FileStream(keyFilePath, FileMode.Open, FileAccess.Read)) {
var credential = GoogleCredential.FromStream(stream);
if (credential.IsCreateScopedRequired) {
credential.CreateScoped(scopes);
}
// Create the service.
Google.Apis.Drive.v3.DriveService service = new Google.Apis.Drive.v3.DriveService(new BaseClientService.Initializer() {
HttpClientInitializer = credential,
ApplicationName = "MyDrive",
});
return service;
}
} catch (Exception ex) {
Console.WriteLine(ex.InnerException);
return null;
}
}
I have looked at the JSON file in notepad and it seems encrypted.
"ewogICJ0eXBlIjogInNlcnZpY2VfYWNjb3VudCIsCiAgInByb2plY3RfaWQiOiAicmFkaWFudC1tZXJjdXJ5LTEyMjkwNyIsCiAgIn.........."
Is it ok to continue using the P12 ?
This works for me using the JSON credentials file from the Google Developers Console. I am using the Analytics Service, but just swap out the appropriate names for the Drive service:
private AnalyticsReportingService service;
public async Task GetAuthorizationByServiceAccount()
{
string[] scopes = new string[] { AnalyticsReportingService.Scope.AnalyticsReadonly }; // Put your scopes here
var keyFilePath = AppContext.BaseDirectory + #"KeyFile.json";
//Console.WriteLine("Key File: " + keyFilePath);
var stream = new FileStream(keyFilePath, FileMode.Open, FileAccess.Read);
var credential = GoogleCredential.FromStream(stream);
credential = credential.CreateScoped(scopes);
service = new AnalyticsReportingService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "<Your App Name here>",
});
}
Make sure that you are downloading proper file...
GoogleCredential.FromStream(stream)
works with JSON file. Its should look something like this:
{
"type": "service_account",
"project_id": "",
"private_key_id": "",
"private_key": "-----BEGIN PRIVATE KEY-----
---END PRIVATE KEY-----\n",
"client_email": "",
"client_id": "",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": ""
}
You can get this file at https://console.developers.google.com/apis/credentials by clicking Download JSON button on the right side of the grid showing client IDs. Just make sure that Type for selected ID is "Service account client".

Google Tasks API Auth

I have to perform a very simple task to get all the tasks of the account Google through Google Api.
Before i started this questions i read documentation, try search to answers, read tech forums, etc
First of all, I configured Developers Console:
- turned ON required API's: such as Tasks API
- Create and download to my project all generic keys: WEB, SERVICE + P12 KEY, INSTALL APP
- also, Installed in project all requiered Nugets: Auth, Client, Core, Tasks.v1
May be i wrong, but matter of great concern (Common Questions):
1. Why need too many variants of keys?
Google IP is only service and it shall be necessary only one token (key)
For software there is no fundamental difference between back-end, desktop, desktop on IOS/Android/Blackberry, service,...
Software need only get/set data from/to some Google Account.
It's very confuse.
2. Google Developers Console - is not simple and clear tools for ordinal user which want get ability to sync with google apps.
Why Google not have simple trust Token generation for account ?
3. Google documentation is not fully and complex. Too many blank places: such as REST queries - not see any correct and work samples with test data and headers.
May any known - where it is ?
Ok, let's begin practice - simple Desktop app on VS2012:
4. Most of google samples said that i need to use GoogleWebAuthorizationBroker
The Code below is similar as all codes in documenation
client_secret.json - JSON files from Google Developers Console
I were try all JSON files with secret codes and also manually set secrets
But nothing, GoogleWebAuthorizationBroker.AuthorizeAsync try to open bad browser window and ends
UserCredential credential;
var stream = new FileStream(#"..." + client_secret.json, FileMode.Open, FileAccess.Read);
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[] { TasksService.Scope.Tasks },
"user",
CancellationToken.None,
new FileDataStore("Tasks.Auth.Store")).Result;
// Create the service.
var service = new TasksService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Api Project"
});
TaskLists results = service.Tasklists.List().Execute();
What is Wrong ???
5. Ок, try to use this:
GoogleAuthorizationCodeFlow flow;
using (var stream = new FileStream(#"..." + client_secret.json, FileMode.Open, FileAccess.Read))
{
flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
DataStore = new FileDataStore("Tasks.Auth.Store"),
ClientSecrets = GoogleClientSecrets.Load(stream).Secrets,
Scopes = new[] { TasksService.Scope.Tasks }
});
}
var result = new AuthorizationCodeWebApp(flow, "", "")
.AuthorizeAsync("user_id", CancellationToken.None).Result;
// The data store contains the user credential, so the user has been already authenticated.
TasksService service = new TasksService(new BaseClientService.Initializer
{
ApplicationName = "API Project",
HttpClientInitializer = result.Credential
});
var lists = service.Tasklists.List().Execute();
Always - not auth.
I was try changed "user_id" - but nothing
What is wrong ???
5. Ок, try to use this:
string serviceAccountEmail = "xxx#developer.gserviceaccount.com";
var certificate = new X509Certificate2(#"...\\key.p12", "notasecret", X509KeyStorageFlags.Exportable);
//var certificate = new X509Certificate2(#"key.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { TasksService.Scope.Tasks }
}.FromCertificate(certificate));
// Create the service.
var service = new TasksService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "API Project",
});
var lists = service.Tasklists.List().Execute();
if (lists.Items != null)
{
foreach (TaskList lst in lists.Items)
{
var title = lst.Title;
var tasks = service.Tasks.List(lst.Id).Execute();
if (tasks.Items != null)
{
foreach (Task tsk in tasks.Items)
{
}
}
}
}
!!! I have access, but not for my account.
Google return only one EMPTY enemy task list (NOT my 2 lists with many tasks in my account)
What is wrong ???
P.S. Please help. I am in deadlock. I did not expect that Google so bad.
Please, do not redirect me to another forums or topics. I all read before.
Thanks

YouTube Data API v.3.0 Unable to Add/Delete Video from Playlist - InsufficientPermissions Exception

All day long I'm trying to delete/add video to my YouTube playlists. I'm using the YouTube Data API v.3.0. for .NET C#.
I have already created a project in Google Developer Console and got my client secrets JSON file. Also my code for obtaining list items is working ok which means that only the PUT operations are not working as expected. I have used almost the same code as in the google developers site code examples.
Authentication method:
private async Task<YouTubeService> GetYouTubeService(string userEmail)
{
UserCredential credential;
using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[]
{
YouTubeService.Scope.Youtube,
YouTubeService.Scope.Youtubepartner,
YouTubeService.Scope.YoutubeUpload,
YouTubeService.Scope.YoutubepartnerChannelAudit,
YouTubeService.Scope.YoutubeReadonly
},
userEmail,
CancellationToken.None,
new FileDataStore(this.GetType().ToString()));
}
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = this.GetType().ToString()
});
return youtubeService;
}
Add video to playlist code:
private async Task AddSongToPlaylistAsync(string userEmail, string songId, string playlistId)
{
var youtubeService = await this.GetYouTubeService(userEmail);
var newPlaylistItem = new PlaylistItem();
newPlaylistItem.Snippet = new PlaylistItemSnippet();
newPlaylistItem.Snippet.PlaylistId = playlistId;
newPlaylistItem.Snippet.ResourceId = new ResourceId();
newPlaylistItem.Snippet.ResourceId.Kind = "youtube#video";
newPlaylistItem.Snippet.ResourceId.VideoId = songId;
newPlaylistItem = await youtubeService.PlaylistItems.Insert(newPlaylistItem, "snippet").ExecuteAsync();
}
this is the message that I receive when I try to add a new video to the specified playlist:
Google.Apis.Requests.RequestError
Insufficient Permission [403]
Errors [
Message[Insufficient Permission] Location[ - ] Reason[insufficientPermissions] Domain[global]
]
I'll really appreciate any available help because I didn't find anything useful googling.
Thank you in advance!
I encountered the same issue but if anyone is having issue with the inefficient permission when adding a video to a playlist, you will need to have the YouTubeService.Scope.Youtube (which looks like you already have).
var scopes = new[]
{
YouTubeService.Scope.Youtube
};
If you however added the scope after you already have given the permission, you will need to revoke the client by going to this page manage permissions. You will have to look for your specific client. After you've done that, you can rerun you app and request for permission once again.
Another option is to create a new clientId and clientSecret again and make sure you have the right scope to begin with. I hope this helps.

Categories

Resources