I am trying to use Batch Requests with the Google Calendar API for .NET. What I am finding is that if there is any problem with an event in the batch it raises an exception and the whole batch fails with no easy way to check which event has the problem.
For example using their code
UserCredential credential;
using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[] { CalendarService.Scope.Calendar },
"user", CancellationToken.None, new FileDataStore("Calendar.Sample.Store"));
}
// Create the service.
var service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Google Calendar API Sample",
});
// Create a batch request.
var request = new BatchRequest(service);
var eventId = "***Bad event id***";
request.Queue<Event>(service.Events.Update(
new Event
{
Summary = "Learn how to execute a batch request",
Start = new EventDateTime() { DateTime = new DateTime(2014, 1, 1, 10, 0, 0) },
End = new EventDateTime() { DateTime = new DateTime(2014, 1, 1, 12, 0, 0) }
}, calendar.Id, eventId),
(content, error, i, message) =>
{
// This is never called!!!
});
await request.ExecuteAsync();
Using this example to update an event, if I enter a bad event id in the update, it will raise an exception and the whole batch will fail. This would be less bad if the callback would be called and let me know which one, but that doesn't seem to ever even be called
What I am finding is that if there is any problem with an event in the batch it raises an exception and the whole batch fails with no easy way to check which event has the problem.
Yes this is true.
The only solution is to check if the request fails you will need to do an event list to find out which events were created and which were not. Then resend the ones that failed. In my experience most often they all fail and none are created but its not something that you can rely upon, so you need to check.
You will also find that when using batching that you may hit the user flood protection quota issues as all the requests get dumped at the server at the same time increasing your chance for a flooding error. You will then be left with trying to figure out which one failed and which one passed.
The only thing batching saves you is the HTTP Calls. Nothing else.
Batching introduces more issues then its worth, IMO.
Related
Events inserted using google's api and service account shows up fine when I view the calendar, but they are not included when I print the calendar.
Events manually inserted are printed as expected. I am using the following code.
string[] scopes = new string[] { CalendarService.Scope.Calendar };
GoogleCredential credential;
using (var stream = new FileStream("mikeServiceAccount.json", FileMode.Open,
FileAccess.Read))
{
credential = GoogleCredential.FromStream(stream).CreateScoped(scopes);
}
// Create the Calendar service.
var service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Calendar Authentication Sample",
});
Event myEvent = new Event
{
Summary = "Summary Title",
Description = "event description"
Location = "Nashville, TN"
Start = new EventDateTime()
{
Date = "2018-10-19"
},
End = new EventDateTime()
{
Date = "2018-10-19"
}
};
service.Events.Insert(myEvent,"b4sbsrsdf9r82sbj0#group.calendar.google.com").Execute();
Here is a composite screen shot of what I see when I view and print the calendar. https://i.stack.imgur.com/8FqAk.png
I was able to work this out, but it was really just ended up being a proof of concept, so I don't have code I look back on. As I remember it the problem ended up being the value I used as my endDate. I don't remember specifics, but maybe I used the same date as the startDate and added the end time of midnight. Something like that anyway. Good luck and sorry I couldn't be more helpful.
Using the information provided by mikeh as a hint,
we were able to resolve the issue "All-day events are not displayed when printing".
The solution in my case by...
setting the start to "YYYY-MM-DDT00:00:00" and...
setting the end to 00:00:00 on the "next day" of the start.
Thank you.
start: {
dateTime : new Date("2020-01-25T00:00:00").toISOString()
,timeZone: 'Asia/Tokyo'
}
,end: {
dateTime : new Date("2020-01-26T00:00:00").toISOString()
,timeZone: 'Asia/Tokyo'
}
I am attempting to write some code that fetches emails from Gmail using the Gmail API documented here. I have successfully managed to retrieve a list of emails, and to get details of individual emails.
What I would like to do now is to use a BatchRequest to get details of multiple emails in a single call, but when I try this I get a 401 error with the message:
Google.Apis.Requests.RequestError Login Required [401] Errors [
Message[Login Required] Location[Authorization - header]
Reason[required] Domain[global] ]
In the GetMessageInfo method in the class below, there are three calls to the API:
Messages.List This successfully returns a list of messages
Messages.Get This successfully returns details of a single message
Finally, I attempt the same Messages.Get as in Step 2, but this time using a BatchRequest, and this fails with the above error.
I am using the same service object each time, and in the case of Steps 2 & 3, I am using the same request.
QUESTION: Why can I get message details with a single request but not as part of a batch request?
public class ProofOfConcept
{
public void GetMessageInfo()
{
GmailService service = GetService();
UsersResource.MessagesResource.ListRequest request = service.Users.Messages.List("me");
request.MaxResults = 1;
ListMessagesResponse response = request.Execute();
Message message = response.Messages.First();
Message fullMessageBySingleRequest = PerformSingleRequest(service, message.Id);
Message fullMessageByBatchRequest = PerformBatchRequest(service, message.Id, out RequestError error);
}
private Message PerformSingleRequest(GmailService service, string messageId)
{
UsersResource.MessagesResource.GetRequest request = service.Users.Messages.Get("me", messageId);
Message message = request.Execute();
return message;
}
private Message PerformBatchRequest(GmailService service, string messageId, out RequestError err)
{
UsersResource.MessagesResource.GetRequest messageRequest = service.Users.Messages.Get("me", messageId);
var batchRequest = new BatchRequest(service);
Message message = null;
RequestError requestError = null;
batchRequest.Queue<Message>(
messageRequest,
(content, error, i, msg) =>
{
message = content;
requestError = error;
});
batchRequest.ExecuteAsync().Wait();
err = requestError;
return message;
}
private GmailService GetService()
{
UserCredential credential;
using (var stream = new FileStream(
#".\ClientSecret\client_secret.json",
FileMode.Open,
FileAccess.Read))
{
string credPath = Environment.GetFolderPath(
Environment.SpecialFolder.Personal);
credPath = Path.Combine(
credPath,
".credentials/gmail-dotnet-quickstart.json");
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[] {GmailService.Scope.GmailReadonly},
"user",
CancellationToken.None,
new FileDataStore(credPath, true))
.Result;
}
// Create Gmail API service.
var service = new GmailService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = "Foo"
});
return service;
}
}
OK this got to big for a comment.
For starters I have never bothered with batching on the Google apis. The code linked I have never gotten to work properly (Imo no fault of the code), I have tried it with several Google APIs(analytics and drive). I have never found the Google batching end point to be very stable. If one request in the batch request fails or is to slow then they all fail you will get a 500 error back.
Second you are not really going to win anything using batching IMO. The quota hits will be the same. Sending five requests one at a time and sending one batch with the same five requests is still going to be five against your quota. Also there is a limit to how many requests you can put in a batch request it depends on the API. You cant put 100 requests in a batch request i think the limit is more like 10.
The only thing you may win is the back and forth on the server which if you have a faster internet connection shouldn't really matter.
All that being said if you really want to spend the time getting batching working let me know i would be happy to give it a try again. I just think you should consider wither its worth your time or not.
I've implemented a c# app which creates calendar events using the calendar API.
In order to keep the calendar in sync with our local db I've created a watch on the calendar. The following code does this and, as far as I can see, a watch is created.
Channel watchChannel = new Channel()
{
Id = ConfigurationManager.AppSettings["GOOGLE_CALENDAR_WATCH_NAME"],
Type = "web_hook",
Address = ConfigurationManager.AppSettings["GOOGLE_CALENDAR_SYNC_TRIGGER_CALLBACK_URL"],
Expiration = (unixTimestamp + NINETY_DAYS_IN_SECONDS) * 1000 //milliseconds
};
try
{
logger.Debug("Creating calendar watch with the name " +
ConfigurationManager.AppSettings["GOOGLE_CALENDAR_WATCH_NAME"] + " for calendar with id " + remoteCalendarId);
Channel returnChannel = service.Events.Watch(watchChannel, remoteCalendarId).Execute();
My problem is that the callback URL isn't getting called (i have confirmed ownership of the domain and authenticated it for the user, that shouldn't be the issue).
How do I debug this? Is there anywhere I can look at the attempts google is
making to call the callback URL?
I say that as far as I can see everything is created ok, but maybe I'm wrong, which property in the
returnChannel should I be looking at?
Is there any way to list all created watches/channels for a particular calendar? If so, which API is that exposed through?
04.04 - A bit more information:
These are the parameters set on the outgoing call (watchChannel) and return-object (returnChannel).
watchChannel
Address "https://a.domain.com/api/schedule/syncDbAndSchedule"
ETag = null
Expiration = 1491309746000
Id = "my_id_watch_dev"
Kind = null
Params__ = null
Payload = null
ResourceId = null
ResourceUri = null
Token = null
Type = "web_hook"
returnChannel
Address = null
ETag = null
Expiration = 1491309746000
Id = "my_id_watch_dev"
Kind = "api#channel"
Params__ = null
Payload = null
ResourceId = "t6uxfXzXXXXXXXXXXsC9ZEqMdzU"
ResourceUri = "https://www.googleapis.com/calendar/v3/calendars/a.domain.com_nsqpp2XXXXXX93olf0ul40sphg#group.calendar.google.com/events?maxResults=250&alt=json"
Token = null
Type = null
Looking at it again I've got a few more questions:
Can I be sure the above returned a HTTP-200 response? The client-libs seem to abstract away the actual request/response and I can't find any trace of it in the objects I'm looking at. As the 4xx responses I've gotten have been transformed into exceptions that's what I'd expect for any non-200 response but can I be sure of that?
Is there really no way to track the attempts google is making whilst calling the callback URL? Since there seems to be no way to get a hold of a created watch it kind of surprises me there is no GUI where I can track this. Makes hunting for errors really hard.
Code to authenticate
I use the following code to authenticate the system user and then make it act in the guise of a 'normal' non-system account (since a pure system-account seemed a tricky way to go if you actually wanted to look at the calendar too).
ServiceAccountCredential credential =
GoogleCredential.FromJson(GOOGLE_SYSTEM_USER_AUTH_INFORMATION)
.CreateScoped(Scopes)
.UnderlyingCredential as ServiceAccountCredential;
var userField =
typeof(ServiceAccountCredential).GetField("user", BindingFlags.NonPublic | BindingFlags.Instance);
userField?.SetValue(credential, GOOGLE_CALENDAR_USERNAME); //Act in the guise of the normal user GOOGLE_CALENDAR_USERNAME
service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
on a whim I visited the ResourceURI returned. It gives me
{
"error": {
"errors": [
{
"domain": "usageLimits",
"reason": "dailyLimitExceededUnreg",
"message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup.",
"extendedHelp": "https://code.google.com/apis/console"
}
],
"code": 403,
"message": "Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup."
}
}
Is
this the status for the watch? If so, why didn't I get a 403 whilst creating it?
Am I really Unautenticated or is it just that my request through a browser is?
The callback url must be https (certificate) otherwise it will not be called!
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
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.