I' am creating dynamically some google sheet given an array of data on one of my endpoints.
it creates successfully, but when I open the generated url, I get the following:
The documentation is not very clear, but I wish to add permissions to a given domain (or set of people) every time I generate a new sheet.
code:
private static readonly string[] Scopes = { SheetsService.Scope.Drive, SheetsService.Scope.Spreadsheets };
public string Create(string templateName, List<IList<object>> values, int numberOfRows)
{
var sheetId = 0;
var spreadSheetId = string.Empty;
var initializer = new ServiceAccountCredential.Initializer(SheetsOptionsConstants.ServiceAccountEmail)
{
Scopes = Scopes
}.FromPrivateKey(SheetsOptionsConstants.PrivateKey);
var credential = new ServiceAccountCredential(initializer);
// Create Google Sheets API service.
var service = new SheetsService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = applicationName
});
// Create a new sheet
var sheetTitle = $"[{templateName}] {_clock.Now:yyyy-MM-dd HH-mm}";
try
{
var sheet = new BatchUpdateSpreadsheetRequest
{
Requests = new List<Request>
{
new Request
{
AddSheet = new AddSheetRequest
{
Properties= new SheetProperties
{
Title = sheetTitle
}
}
}
}
};
var createRequest = new Spreadsheet
{
Properties = new SpreadsheetProperties
{
Title = sheetTitle
}
};
var spreadSheetCreate = service.Spreadsheets.Create(createRequest).Execute();
spreadSheetId = spreadSheetCreate.SpreadsheetId;
var request = service.Spreadsheets.BatchUpdate(sheet, spreadSheetId);
var result = request.Execute();
sheetId = result.Replies.First().AddSheet.Properties.SheetId.GetValueOrDefault(0);
// Add data in sheet
var range = $"'{sheetTitle}'!A1:X{numberOfRows + 1}";
var valueRange = new ValueRange
{
Range = range,
Values = values
};
var valuesRequest = service.Spreadsheets.Values.Update(valueRange, spreadSheetId, range);
valuesRequest.ValueInputOption = SpreadsheetsResource.ValuesResource.UpdateRequest.ValueInputOptionEnum.USERENTERED;
valuesRequest.Execute();
_logger.LogInformation("Create sheet with name {sheetTitle} and {count} lines", sheetTitle, numberOfRows);
}
catch (Exception exception)
{
_logger.LogError(exception, "Error to create sheet with name {sheetTitle}", sheetTitle);
}
return string.Format(TemplateUrl, spreadSheetId, sheetId);
}
Thanks in advance
You need to use the Drive API to create the permissions for the Sheet. To do so, initialize the Drive Service and create a permissions object. Then, apply it to the Sheet Id using the method create().
You should include the following to your code:
using Google.Apis.Drive.v3;
using Google.Apis.Drive.v3.Data; //This might be not necessary if you already use Drive.v3
The required scopes:
https://www.googleapis.com/auth/drive.file
Create the permissions object:
//(example to give writing permissions to a domain)
Permission perms = new Permission();
perms.Role = "writer";
perms.Type = "domain";
perms.Domain = "your_domain";
And then the request:
// Create Drive API service.
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
service.Permissions.Create(perms, sheetId).Execute();
References:
.NET Quickstart
Manage sharing
Permissions from the Drive Api
I am having problems loading a playlist of videos from Youtube. I follow this guide but cant figure out what is wrong because I don't get an error.
var YouTubeService = new YouTubeService(new BaseClientService.Initializer() { ApiKey = "MyAPIID"});
var ChannelListRequest = YouTubeService.Channels.List("contentDetails");
ChannelListRequest.ForUsername = "YoutubeUser";
var ListResponse = ChannelListRequest.Execute();
foreach (var channel in ListResponse.Items) //No content in ListResponse.Items
When I execute the request it returns a empty response. The API Id is correct because it becomes error if I use a old one. I have tried with the username and id from the channel but none worked. What am I missing?
Alright I tried some things and I managed to retrieve my playlists of my channel like so:
var service = new YouTubeService(new BaseClientService.Initializer()
{
ApiKey = "yourapikey",
ApplicationName = this.GetType().Name
});
var playListRequest = service.Playlists.List("snippet");
playListRequest.ChannelId = "yourchannelid";
var result = await playListRequest.ExecuteAsync();
With the playlist Id's you get from this response you can retrieve the video's like so:
var playListItemsRequest = service.PlaylistItems.List("snippet");
playListItemsRequest.PlaylistId = "yourplaylistid";
var result = await playListItemsRequest.ExecuteAsync();
I'm trying to use Google Calendar API v3, but i have problems while running the codes, it always gives me that error :
An exception of type 'System.AggregateException' occurred in mscorlib.ni.dll but was not handled in user code
Additional information: One or more errors occurred.
I don't know why it does, also It should work as well. Here is a screenshot for it :
Also my codes are :
UserCredential credential;
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new Uri("ms-appx:///Assets/client_secrets.json"),
Scopes,
"user",
CancellationToken.None).Result;
// Create Google Calendar API service.
var service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
var calendarService = new CalendarService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = "Windows 10 Calendar sample"
});
var calendarListResource = await calendarService.CalendarList.List().ExecuteAsync();
If you can at least help with calling it through REST API, that would be great too, but you must consider that it's UWP, so it has another way to get it work as well.
As i already tried through REST API, but i always get "Request error code 400".
Thanks for your attention.
The Google API Client Library for .NET does not support UWP by now. So we can't use Google.Apis.Calendar.v3 Client Library in UWP apps now. For more info, please see the similar question: Universal Windows Platform App with google calendar.
To use Google Calendar API in UWP, we can call it through REST API. To use the REST API, we need to authorize requests first. For how to authorize requests, please see Authorizing Requests to the Google Calendar API and Using OAuth 2.0 for Mobile and Desktop Applications.
After we have the access token, we can call Calendar API like following:
var clientId = "{Your Client Id}";
var redirectURI = "pw.oauth2:/oauth2redirect";
var scope = "https://www.googleapis.com/auth/calendar.readonly";
var SpotifyUrl = $"https://accounts.google.com/o/oauth2/auth?client_id={clientId}&redirect_uri={Uri.EscapeDataString(redirectURI)}&response_type=code&scope={Uri.EscapeDataString(scope)}";
var StartUri = new Uri(SpotifyUrl);
var EndUri = new Uri(redirectURI);
// Get Authorization code
WebAuthenticationResult WebAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, StartUri, EndUri);
if (WebAuthenticationResult.ResponseStatus == WebAuthenticationStatus.Success)
{
var decoder = new WwwFormUrlDecoder(new Uri(WebAuthenticationResult.ResponseData).Query);
if (decoder[0].Name != "code")
{
System.Diagnostics.Debug.WriteLine($"OAuth authorization error: {decoder.GetFirstValueByName("error")}.");
return;
}
var autorizationCode = decoder.GetFirstValueByName("code");
//Get Access Token
var pairs = new Dictionary<string, string>();
pairs.Add("code", autorizationCode);
pairs.Add("client_id", clientId);
pairs.Add("redirect_uri", redirectURI);
pairs.Add("grant_type", "authorization_code");
var formContent = new Windows.Web.Http.HttpFormUrlEncodedContent(pairs);
var client = new Windows.Web.Http.HttpClient();
var httpResponseMessage = await client.PostAsync(new Uri("https://www.googleapis.com/oauth2/v4/token"), formContent);
if (!httpResponseMessage.IsSuccessStatusCode)
{
System.Diagnostics.Debug.WriteLine($"OAuth authorization error: {httpResponseMessage.StatusCode}.");
return;
}
string jsonString = await httpResponseMessage.Content.ReadAsStringAsync();
var jsonObject = Windows.Data.Json.JsonObject.Parse(jsonString);
var accessToken = jsonObject["access_token"].GetString();
//Call Google Calendar API
using (var httpRequest = new Windows.Web.Http.HttpRequestMessage())
{
string calendarAPI = "https://www.googleapis.com/calendar/v3/users/me/calendarList";
httpRequest.Method = Windows.Web.Http.HttpMethod.Get;
httpRequest.RequestUri = new Uri(calendarAPI);
httpRequest.Headers.Authorization = new Windows.Web.Http.Headers.HttpCredentialsHeaderValue("Bearer", accessToken);
var response = await client.SendRequestAsync(httpRequest);
if (response.IsSuccessStatusCode)
{
var listString = await response.Content.ReadAsStringAsync();
//TODO
}
}
}
I have the Google .NET Client working in my UWP app. The trick is that you have to put it in a .NET Standard 2.0 Class Library, expose the API services you need, and then reference that library from your UWP app.
Also, you have to handle the getting the auth token yourself. It's not that much work and the Drive APIs and Calendar APIs work just fine (the only ones I've tried). You can see that I pass in a simple class that contains the auth token and other auth details to a method called Initialize.
Here is the single class I used in the .NET Standard 2.0 class library:
namespace GoogleProxy
{
public class GoogleService
{
public CalendarService calendarService { get; private set; }
public DriveService driveService { get; private set; }
public GoogleService()
{
}
public void Initialize(AuthResult authResult)
{
var credential = GetCredentialForApi(authResult);
var baseInitializer = new BaseClientService.Initializer { HttpClientInitializer = credential, ApplicationName = "{your app name here}" };
calendarService = new Google.Apis.Calendar.v3.CalendarService(baseInitializer);
driveService = new Google.Apis.Drive.v3.DriveService(baseInitializer);
}
private UserCredential GetCredentialForApi(AuthResult authResult)
{
var initializer = new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "{your app client id here}",
ClientSecret = "",
},
Scopes = new string[] { "openid", "email", "profile", "https://www.googleapis.com/auth/calendar.readonly", "https://www.googleapis.com/auth/calendar.events.readonly", "https://www.googleapis.com/auth/drive.readonly" },
};
var flow = new GoogleAuthorizationCodeFlow(initializer);
var token = new TokenResponse()
{
AccessToken = authResult.AccessToken,
RefreshToken = authResult.RefreshToken,
ExpiresInSeconds = authResult.ExpirationInSeconds,
IdToken = authResult.IdToken,
IssuedUtc = authResult.IssueDateTime,
Scope = "openid email profile https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/calendar.events.readonly https://www.googleapis.com/auth/drive.readonly",
TokenType = "bearer" };
return new UserCredential(flow, authResult.Id, token);
}
}
}
In order to get the Auth token from google, you have to use custom schemes. Register your app as an 'iOS' app on the google services console and put in a URI scheme (something unique). Then add this scheme to your UWP manifest under Declarations->Protocol. Handle it in your App.xaml.cs:
protected override void OnActivated(IActivatedEventArgs args)
{
base.OnActivated(args);
if (args.Kind == ActivationKind.Protocol)
{
ProtocolActivatedEventArgs protocolArgs = (ProtocolActivatedEventArgs)args;
Uri uri = protocolArgs.Uri;
Debug.WriteLine("Authorization Response: " + uri.AbsoluteUri);
locator.AccountsService.GoogleExternalAuthWait.Set(uri.Query);
}
}
That GoogleExternalAuthWait comes from some magical code I found about how to create an asynchronous ManualResetEvent. https://blogs.msdn.microsoft.com/pfxteam/2012/02/11/building-async-coordination-primitives-part-1-asyncmanualresetevent/ It looks like this (I only converted it to generic).
public class AsyncManualResetEvent<T>
{
private volatile TaskCompletionSource<T> m_tcs = new TaskCompletionSource<T>();
public Task<T> WaitAsync() { return m_tcs.Task; }
public void Set(T TResult) { m_tcs.TrySetResult(TResult); }
public bool IsReset => !m_tcs.Task.IsCompleted;
public void Reset()
{
while (true)
{
var tcs = m_tcs;
if (!tcs.Task.IsCompleted ||
Interlocked.CompareExchange(ref m_tcs, new TaskCompletionSource<T>(), tcs) == tcs)
return;
}
}
}
This is how you start the Google Authorization. What happens is it launches an external browser to begin the google signing process and then wait (that's what the AsyncManualResetEvent does). When you're done, Google will launch a URI using your custom scheme. You should get a message dialog saying the browser is trying to open an app... click ok and the AsyncManualResetEvent continues and finishes the auth process. You'll need to make a class that contains all the auth info to pass to your class library.
private async Task<AuthResult> AuthenticateGoogleAsync()
{
try
{
var stateGuid = Guid.NewGuid().ToString();
var expiration = DateTimeOffset.Now;
var url = $"{GoogleAuthorizationEndpoint}?client_id={WebUtility.UrlEncode(GoogleAccountClientId)}&redirect_uri={WebUtility.UrlEncode(GoogleRedirectURI)}&state={stateGuid}&scope={WebUtility.UrlEncode(GoogleScopes)}&display=popup&response_type=code";
var success = Windows.System.Launcher.LaunchUriAsync(new Uri(url));
GoogleExternalAuthWait = new AsyncManualResetEvent<string>();
var query = await GoogleExternalAuthWait.WaitAsync();
var dictionary = query.Substring(1).Split('&').ToDictionary(x => x.Split('=')[0], x => Uri.UnescapeDataString(x.Split('=')[1]));
if (dictionary.ContainsKey("error"))
{
return null;
}
if (!dictionary.ContainsKey("code") || !dictionary.ContainsKey("state"))
{
return null;
}
if (dictionary["state"] != stateGuid)
return null;
string tokenRequestBody = $"code={dictionary["code"]}&redirect_uri={Uri.EscapeDataString(GoogleRedirectURI)}&client_id={GoogleAccountClientId}&access_type=offline&scope=&grant_type=authorization_code";
StringContent content = new StringContent(tokenRequestBody, Encoding.UTF8, "application/x-www-form-urlencoded");
// Performs the authorization code exchange.
using (HttpClientHandler handler = new HttpClientHandler())
{
handler.AllowAutoRedirect = true;
using (HttpClient client = new HttpClient(handler))
{
HttpResponseMessage response = await client.PostAsync(GoogleTokenEndpoint, content);
if (response.IsSuccessStatusCode)
{
var stringResponse = await response.Content.ReadAsStringAsync();
var json = JObject.Parse(stringResponse);
var id = DecodeIdFromJWT((string)json["id_token"]);
var oauthToken = new AuthResult()
{
Provider = AccountType.Google,
AccessToken = (string)json["access_token"],
Expiration = DateTimeOffset.Now + TimeSpan.FromSeconds(int.Parse((string)json["expires_in"])),
Id = id,
IdToken = (string)json["id_token"],
ExpirationInSeconds = long.Parse((string)json["expires_in"]),
IssueDateTime = DateTime.Now,
RefreshToken = (string)json["refresh_token"]
};
return oauthToken;
}
else
{
return null;
}
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
return null;
}
}
https://www.youtube.com/channel/UC-9-kyTW8ZkZNDHQJ6FgpwQ
public async System.Threading.Tasks.Task<List<ViewModel.YoutubeVideo>> GetYoutubeMusic()
{
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
ApiKey = "....",
ApplicationName = this.GetType().ToString()
});
var searchListRequest = youtubeService.Channels.List("snippet");
searchListRequest.Id= "UC-9-kyTW8ZkZNDHQJ6FgpwQ";
searchListRequest.MaxResults = 25;
//Call the search.list method to retrieve results...
var searchListResponse = await searchListRequest.ExecuteAsync();
List<ViewModel.YoutubeVideo> arrays = new List<ViewModel.YoutubeVideo>();
foreach (var searchResult in searchListResponse.Items)
{
product = new ViewModel.YoutubeVideo();
product.id = searchResult.Id;
product.Name = searchResult.Snippet.Title;
product.Thumb100Uri = searchResult.Snippet.Thumbnails.Default.Url;
product.Thumb200Uri = searchResult.Snippet.Thumbnails.Medium.Url;
arrays.Add(product);
}
return arrays;
}
just be get information from this channel but no video...
I don't understand about that. Please to solve it.
Because you are not calling the search.list method. You are calling the channels.list method in your code.
But if you already have the id of the channel, you should just retrieve that channel's uploaded videos.
I am using the Google Analytics Api to get web property information from my Analytics account.
When I log into analaytics though, I only have one website, but through the api I get several (old and deleted sites)
My code is like this:
var provider = new WebServerClient(GoogleAuthenticationServer.Description)
{
ClientIdentifier = _appId,
ClientSecret = _appSecret
};
var auth = new OAuth2Authenticator<WebServerClient>(provider, x => new AuthorizationState { AccessToken = token });
var analyticsService = new AnalyticsService(auth);
var accounts = analyticsService.Management.Accounts.List().Fetch();
foreach (var account in accounts.Items)
{
var webProperties = analyticsService.Management.Webproperties.List(account.Id).Fetch();
// todo: determine if web property is still in use?
}
From code how can I tell which ones are still active?
So after a bit more digging.
It seems there is no flag or anything like that indicating it has been removed, but if you keep digging into the result set you will notice that at the profile level, a profile that doesn't have child items seems to be a deleted one.
Which makes sense I guess there wouldn't be a profile associated with those that have been removed.
var provider = new WebServerClient(GoogleAuthenticationServer.Description)
{
ClientIdentifier = _appId,
ClientSecret = _appSecret
};
var auth = new OAuth2Authenticator<WebServerClient>(provider, x => new AuthorizationState { AccessToken = token });
var analyticsService = new AnalyticsService(auth);
var accounts = analyticsService.Management.Accounts.List().Fetch();
var result = new List<Profile>();
foreach (var account in accounts.Items)
{
var webProperties = analyticsService.Management.Webproperties.List(account.Id).Fetch();
foreach (var webProperty in webProperties.Items)
{
var profiles = analyticsService.Management.Profiles.List(account.Id, webProperty.Id).Fetch();
if (profiles.Items != null && profiles.Items.Any())
{
// these are the ones we want
result.AddRange(profiles.Items);
}
}
}
}