I am still getting used to Xamarin.Forms and I am on very basic level. I went through many articles for my issue, but to the end couldn't resolve it. So...
Currently I am trying to add Google authentication inside my Xamarin.Forms application, which use Droid and iOS (no WP).
So far I am following guide from here. I am using Xamarin.Auth to authenticate to Google.
Here is some part from my source code.
private async void GoogleSheetsButton_Tapped()
{
string clientId = null;
string redirectUri = null;
if (Device.RuntimePlatform == Device.iOS)
{
clientId = Constants.iOSClientId;
redirectUri = Constants.iOSRedirectUrl;
}
else if (Device.RuntimePlatform == Device.Android)
{
clientId = Constants.AndroidClientId;
redirectUri = Constants.AndroidRedirectUrl;
}
var authenticator = new OAuth2Authenticator(
clientId,
null,
Constants.Scope,
new Uri(Constants.AuthorizeUrl),
new Uri(redirectUri),
new Uri(Constants.AccessTokenUrl),
null,
true);
authenticator.Completed += OnAuthCompleted;
authenticator.Error += OnAuthError;
AuthenticationState.Authenticator = authenticator;
var presenter = new Xamarin.Auth.Presenters.OAuthLoginPresenter();
presenter.Login(authenticator);
}
The problem is coming after my method complete it's work. So after my last line:
presenter.Login(authenticator);
everything looks alright and debugging I am following that compiler goes out of method without any errors, but then I receive exception, which you can see here. It's "No compatible code running".
Here some more information regarding my source code:
Source of "Constants" class used for client ids and URLs
public static class Constants
{
public static string AppName = "....";
// OAuth
// For Google login, configure at https://console.developers.google.com/
public static string iOSClientId = "6.....apps.googleusercontent.com";
public static string AndroidClientId = "6.....apps.googleusercontent.com";
// These values do not need changing
public static string Scope = "https://www.googleapis.com/auth/userinfo.email";
public static string AuthorizeUrl = "https://accounts.google.com/o/oauth2/auth";
public static string AccessTokenUrl = "https://www.googleapis.com/oauth2/v4/token";
public static string UserInfoUrl = "https://www.googleapis.com/oauth2/v2/userinfo";
// Set these to reversed iOS/Android client ids, with :/oauth2redirect appended
public static string iOSRedirectUrl = "com.googleusercontent.apps.6......h:/oauth2redirect";
public static string AndroidRedirectUrl = "com.googleusercontent.apps.6......l:/oauth2redirect";
}
Source of implemented methods for on authentication complete/error, which in fact still I cannot hit because of my error
async void OnAuthCompleted(object sender, AuthenticatorCompletedEventArgs e)
{
var authenticator = sender as OAuth2Authenticator;
if (authenticator != null)
{
authenticator.Completed -= OnAuthCompleted;
authenticator.Error -= OnAuthError;
}
GoogleLoginUser user = null;
if (e.IsAuthenticated)
{
var request = new OAuth2Request("GET", new Uri(Constants.UserInfoUrl), null, e.Account);
var response = await request.GetResponseAsync();
if (response != null)
{
string userJson = await response.GetResponseTextAsync();
user = JsonConvert.DeserializeObject(userJson);
}
if (_account != null)
{
_store.Delete(_account, Constants.AppName);
}
await _store.SaveAsync(_account = e.Account, Constants.AppName);
await DisplayAlert("Email address", user.Email, "OK");
}
}
void OnAuthError(object sender, AuthenticatorErrorEventArgs e)
{
var authenticator = sender as OAuth2Authenticator;
if (authenticator != null)
{
authenticator.Completed -= OnAuthCompleted;
authenticator.Error -= OnAuthError;
}
var message = e.Message;
}
Source of Android MainActivity where I added
public class MainActivity : FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
Forms.Init(this, bundle);
global::Xamarin.Auth.Presenters.XamarinAndroid.AuthenticationConfiguration.Init(this, bundle);
MobileBarcodeScanner.Initialize(Application);
LoadApplication(new App());
}
}
Source of UrlSchemeInterceptorActivity
[Activity(Label = "CustomUrlSchemeInterceptorActivity", NoHistory = true, LaunchMode = LaunchMode.SingleTop)]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable }, DataSchemes = new[] { "com.googleusercontent.apps.6......l" }, DataPath = "/oauth2redirect")]
public class CustomUrlSchemeInterceptorActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
var uri = new Uri(Intent.Data.ToString());
AuthenticationState.Authenticator.OnPageLoading(uri);
Finish();
}
}
Here are the main articles I went through deeply => Link 1, Link 2 and Link 3, but still couldn't resolve the issue.
I am not sure where the error comes from, or can I can I continue debugging it to resolve issue.
Thanks in advance
Solution
Change android compiler to Android 7.0 inside Android project properties. Screenshot
Make sure that inside Android Manifest your target is SDK Version. Screenshot
Update all "Xamarin.Android.*" nuget packages to minimum version 25.4.0.1. Most probably they're currently to 23.3.0. I found problems with dependencies on updating it, so I make manual upload. I went and download manually each package and move it to packages folder. Then I created my own package source and give for path my folder packages and I used it to install already downloaded NuGet packages. Screenshot
After that my issue was solved.
Please update your Android SDK to API 24 or higher and set it as the compile version for your project. And update your referred assemblies for the custom tabs and its dependencies to v25.x.x.
Related
I want to redirect my user from email link to a particular app page.
I do not have a subsequent website, my app is independent.
I have tried intent filters and it does take me to the apps main activity but how do i navigate the user to particular activity is my main roadblock.
Am not interested in app linking I just require deep linking.
I want to know how to to navigate to the particular activity from the link itself directly.
I have tried intent filters in mainactivity.cs along with datascheme.
In my implementation when i send an link inside the email and i click OS asks me how should I proceed
1.By app or 2. Chrome This is fine.
But when i click on app it opens from the main activity.
[IntentFilter(new[] { Android.Content.Intent.ActionView },
AutoVerify = true,
Categories = new[]
{
Android.Content.Intent.CategoryDefault,
Android.Content.Intent.CategoryBrowsable
},
DataScheme = "http",
DataPathPrefix = "",
DataHost = "MyAppName")]
Suppose the url you click is http://myappname?destination=a, you can get the data in activity through:
if (Intent.Data != null)
{
var host = Intent.Data.EncodedAuthority;
var parameter = Intent.Data.GetQueryParameter("destination");
}
As you are using Xamarin.Forms, you should navigate to the specify page on Forms. MessagingCenter is a good choice.
Firstly, register it in App on Forms project:
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new MainPage());
MessagingCenter.Subscribe<object, object>(this, "Navigate", (sender, args) =>
{
if ((string)args == "a")
{
MainPage = new SecondPage();
// or (MainPage as NavigationPage).PushAsync(new SecondPage());
}
});
}
Fire this messaging center when you recieve the data:
if (host == "myappname")
{
MessagingCenter.Send<object, object>(this, "Navigate", parameter);
}
Update
If you don't want to use MessagingCenter. Define a public method in App like:
public void MoveToPage(string pageName)
{
if (pageName == "a")
{
MainPage = new SecondPage();
// or (MainPage as NavigationPage).PushAsync(new SecondPage());
}
}
Then call this when Intent.Data != null in MainActivity:
var formsApp = new App();
LoadApplication(formsApp);
if (Intent.Data != null)
{
var host = Intent.Data.EncodedAuthority;
var parameter = Intent.Data.GetQueryParameter("destination");
formsApp.MovePage(parameter);
}
I guess my question, Understanding Cognito Identities, wasn't specific enough. I still can't figure out how to use a federated identity from a Xamarin app. Here's what I'm trying, but it's really quite random because I can't find any sample code for this task out there. I tried putting a breakpoint on the AddLogin line, and it never gets hit, even though breakpoint two lines up does get hit. There are too many new-to-me technologies in this code for me to know where to begin on tracking down the problem. (I x'd out the Identity pool ID in the code below, but a real one is there.) At this point I'm just trying to get evidence that I can uniquely identify/validate an Amazon account, and maybe add it to my user pool. But I can't even get the code to entirely execute or report an error.
Login().ContinueWith(t => { if (t.Exception != null)
Toast.MakeText(ApplicationContext, t.Exception.ToString(), ToastLength.Long).Show(); });
public async Task Login()
{
CognitoAWSCredentials credentials = new CognitoAWSCredentials(
"us-east-2:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", // Identity pool ID
RegionEndpoint.USEast2 // Region
);
var client = new Amazon.SecurityToken.AmazonSecurityTokenServiceClient(credentials);
var request = new Amazon.SecurityToken.Model.GetFederationTokenRequest("myamazonid#gmail.com");
var response = await client.GetFederationTokenAsync(request);
credentials.AddLogin("www.amazon.com", response.Credentials.SessionToken);
}
It took a good deal of searching, but I think I figured it out. Setting up the services and getting the client ID is not too hard (is well documented) compared to working out the code, so this answer will focus on the code. Google is particularly tricky because of changes made to their OAuth implementation that prevents some forms of authentication from working. In order for Google identities to work with Cognito, APIs need to be up-to-date. Use NuGet to reference the following API versions or later:
Xamarin.Auth 1.5.0.3
Xamarin.Android.Support.v4 25.4.0.2
Xamarin.Android.Support.CustomTabs 25.4.0.2
AWSSDK.CognitoIdentity 3.3.2.14
AWSSDK.Core 3.3.17.8
Validation 2.4.15
Xamarin.Android.Support.Annotations 25.4.0.2
This code is in the main activity:
protected override void OnCreate(Bundle savedInstanceState)
{
// (etc)
credentials = new CognitoAWSCredentials(
"us-east-2:00000000-0000-0000-0000-000000000000", // Identity pool ID
RegionEndpoint.USEast2 // Region
);
// (etc)
}
private void ShowMessage(string message)
{
AlertDialog dlgAlert = new AlertDialog.Builder(this).Create();
dlgAlert.SetMessage(message);
dlgAlert.SetButton("Close", (s, args) => { dlgAlert.Dismiss(); });
dlgAlert.Show();
}
public void Logout()
{
credentials.Clear();
}
public void Login()
{
if (!string.IsNullOrEmpty(credentials.GetCachedIdentityId()) || credentials.CurrentLoginProviders.Length > 0)
{
if (!bDidLogin)
ShowMessage(string.Format("I still remember you're {0} ", credentials.GetIdentityId()));
bDidLogin = true;
return;
}
bDidLogin = true;
auth = new Xamarin.Auth.OAuth2Authenticator(
"my-google-client-id.apps.googleusercontent.com",
string.Empty,
"openid",
new System.Uri("https://accounts.google.com/o/oauth2/v2/auth"),
new System.Uri("com.mynamespace.myapp:/oauth2redirect"),
new System.Uri("https://www.googleapis.com/oauth2/v4/token"),
isUsingNativeUI: true);
auth.Completed += Auth_Completed;
StartActivity(auth.GetUI(this));
}
private void Auth_Completed(object sender, Xamarin.Auth.AuthenticatorCompletedEventArgs e)
{
if (e.IsAuthenticated)
{
var http = new System.Net.Http.HttpClient();
var idToken = e.Account.Properties["id_token"];
credentials.AddLogin("accounts.google.com", idToken);
AmazonCognitoIdentityClient cli = new AmazonCognitoIdentityClient(credentials, RegionEndpoint.USEast2);
var req = new Amazon.CognitoIdentity.Model.GetIdRequest();
req.Logins.Add("accounts.google.com", idToken);
req.IdentityPoolId = "us-east-2:00000000-0000-0000-0000-000000000000";
cli.GetIdAsync(req).ContinueWith((task) =>
{
if ((task.Status == TaskStatus.RanToCompletion) && (task.Result != null))
ShowMessage(string.Format("Identity {0} retrieved", task.Result.IdentityId));
else
ShowMessage(task.Exception.InnerException!=null ? task.Exception.InnerException.Message : task.Exception.Message);
});
}
else
ShowMessage("Login cancelled");
}
Then there's another activity to handle the callback from the redirect URL in the Google authentication process:
[Activity(Label = "GoodleAuthInterceptor")]
[IntentFilter(actions: new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable },
DataSchemes = new[] { "com.mynamespace.myapp" }, DataPaths = new[] { "/oauth2redirect" })]
public class GoodleAuthInterceptor : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Android.Net.Uri uri_android = Intent.Data;
Uri uri_netfx = new Uri(uri_android.ToString());
MainActivity.auth?.OnPageLoading(uri_netfx);
Finish();
}
}
I am developing an App in Xamarin Android, for notifications I am using FCM the Pre-Release package: https://www.nuget.org/packages/Xamarin.Firebase.Messaging/
Now everything works fine if I clean the App data, the OnTokenRefresh event is fired and a new token is generated - when I send a new notification on this Token the notification is sent and received by the device in OnMessageReceived() -
The problem is when I make changes to the code and run the application again, if I use the old token I get the NotRegistered Error when sending a notification, but if I go and clean the App Data, then the OnTokenRefresh() is fired a new token is generated - the new token works.
Similar issue here, but this is GCM (I am using FCM):
Google cloud message 'Not Registered' failure and unsubscribe best practices?
https://stackoverflow.com/a/36856867/1910735
https://forums.xamarin.com/discussion/65205/google-cloud-messaging-issues#latest
My FCMInstanceIdService
[Service, IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
public class FCMInstanceIdService : FirebaseInstanceIdService
{
private string Tag = "FCMInstanceIdService";
public override void OnTokenRefresh()
{
var fcmDeviceId = FirebaseInstanceId.Instance.Token;
if (Settings.DeviceId != fcmDeviceId)
{
var oldDeviceId = Settings.DeviceId;
Settings.DeviceId = fcmDeviceId;
//TODO: update token on DB - Currently OnTokenRefresh is only called when: 1. App data is cleaned, 2. The app is re-installed
//_usersProvider.UpdateUserDeviceId(oldDeviceId, fcmDeviceId);
}
base.OnTokenRefresh();
}
}
My Message Receive Service:
[Service, IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
public class FCMListenerService : FirebaseMessagingService
{
private string Tag = "FCM_Listener_Service";
public override void OnMessageReceived(RemoteMessage message)
{
base.OnMessageReceived(message);
var notification = message.GetNotification();
var data = message.Data;
var title = notification.Title;
var body = notification.Body;
SendNotification(title, body);
}
private void SendNotification(string title, string body)
{
//TODO: Display notification to user
}
}
Manifest:
<application android:label="TBApp" android:theme="#style/TBAppTheme">
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver" android:exported="false" />
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="${applicationId}" />
</intent-filter>
</receiver>
</application>
How do I force refresh the FCM Token in DEBUG mode so I don't have to delete the App Data every time I run the application?
As this issue only happens when running the Application from Visual Studio while debugging the application (not in the version deployed to PlayStore), what I did to solve the issue temporarily is I created the following service:
[Service]
public class FCMRegistrationService : IntentService
{
private const string Tag = "FCMRegistrationService";
static object locker = new object();
protected override void OnHandleIntent(Intent intent)
{
try
{
lock (locker)
{
var instanceId = FirebaseInstanceId.Instance;
var token = instanceId.Token;
if (string.IsNullOrEmpty(token))
return;
#if DEBUG
instanceId.DeleteToken(token, "");
instanceId.DeleteInstanceId();
#endif
}
}
catch (Exception e)
{
Log.Debug(Tag, e.Message);
}
}
}
then in my launch Activity (the activity that loads whenever the Application is opened is do the following:
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
#if DEBUG
if (!IsMyServiceRunning("FCMRegistrationService"))
{
var intent = new Intent(this, typeof(FCMRegistrationService));
StartService(intent);
}
// For debug mode only - will accept the HTTPS certificate of Test/Dev server, as the HTTPS certificate is invalid /not trusted
ServicePointManager.ServerCertificateValidationCallback += (o, certificate, chain, errors) => true;
#endif
}
This will unregister your existing FCMToken and will refresh the Token, so the OnTokenRefresh method will be called, then you will have to write some logic to update the FCMToken on the server.
[Service, IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
public class FCMInstanceIdService : FirebaseInstanceIdService
{
// private string LogTag = "FCMInstanceIdService";
public override void OnTokenRefresh()
{
var fcmDeviceId = FirebaseInstanceId.Instance.Token;
// Settings (is Shared Preferences) - I save the FCMToken Id in shared preferences
// if FCMTokenId is not the same as old Token then update on the server
if (Settings.FcmTokenId != fcmDeviceId)
{
var oldFcmId = Settings.FcmTokenId;
var validationContainer = new ValidationContainer();
// HERE UPDATE THE TOKEN ON THE SERVER
TBApp.Current._usersProvider.UpdateFcmTokenOnServer(oldFcmId, fcmDeviceId, validationContainer);
Settings.FcmTokenId = fcmDeviceId;
}
base.OnTokenRefresh();
}
}
i'm manualy trying to initialize like this
var options = new FirebaseOptions.Builder()
.SetApplicationId("YOURAPPID")
.SetApiKey("YOURAPIKEY")
//.SetDatabaseUrl(Keys.Firebase.Database_Url) //i'M not using it
.SetGcmSenderId("YOURSENDERID")
//.SetStorageBucket(Keys.Firebase.StorageBucket)//i'M not using it
.Build();
try
{ //try to initilize firebase app to get token
FirebaseApp.InitializeApp(Forms.Context, options);//initializeto get token
}
catch
{ //if app already initialized it will throw exception, so get the previous active token and send to your server-database etc
var instanceId = FirebaseInstanceId.Instance;
var token = instanceId.Token;
Service.MyFirebaseMessagingService.RegisterForAndroid(token); //this method sends the token to my server app, you have to write your own
}
So when user opens the app, I'm trying to reinitialize the Firebase app. If it is already initalized it will throw an exception :) I'm taking the token there so it gives me the active registered token. If app is not initialized everything will work on smoothly and so your OnTokenRefresh method will be fired as expected. Hope this helps you.
I am getting below error, I wanted to get all the comments posted on a youtube video.
So basically I am passing video id and I wanted to get all the comments associated with that video
Google.Apis.Requests.RequestError
Insufficient Permission [403]
Errors [Message[Insufficient Permission] Location[ - ] Reason[insufficientPermissions] Domain[global]]
Here is my code:
protected void btnGetVideoDesc_Click(object sender, EventArgs e)
{
string videoId = txtVideoID.Text;
YoutubeVideo video = new YoutubeVideo(videoId);
lblTitle.Text = video.title;
lblPublishedDate.Text = video.publishdate.ToShortDateString();
}
public class YoutubeVideo
{
public string id, title, description ;
public DateTime publishdate;
public YoutubeVideo(string id)
{
this.id = id;
YoutubeAPI.GetVideoInfo(this);
}
}
public class YoutubeAPI
{
private static YouTubeService ytService = Auth();
private static YouTubeService Auth()
{
UserCredential creds;
var service = new YouTubeService();
try
{
using (var stream = new FileStream(#"C:\v-mmarat\Project\EMEA_Development\YoutubeWebCrawling\YoutubeWebCrawling\youtube_client_secret.json", FileMode.Open, FileAccess.Read))
{
creds = GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.Load(stream).Secrets,
new[] { YouTubeService.Scope.YoutubeReadonly }, "user", CancellationToken.None,
new FileDataStore("YoutubeAPI")
).Result;
}
service = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = creds,
ApplicationName = "YoutubeAPI",
ApiKey = "My_API_Key"
});
}
catch (Exception e)
{ }
return service;
}
public static void GetVideoInfo(YoutubeVideo video)
{
try
{
//This code work perfectly
var videoRequest = ytService.Videos.List("snippet");
videoRequest.Id = video.id;
var response = videoRequest.Execute();
if (response.Items.Count > 0)
{
video.title = response.Items[0].Snippet.Title;
video.description = response.Items[0].Snippet.Description;
video.publishdate = response.Items[0].Snippet.PublishedAt.Value;
}
else
{
//error
}
var CommentRequest = ytService.Comments.List("snippet");
videoRequest.Id = video.id;
//Getting error at this line after CommentRequest.Execute();
var Commentresponse = CommentRequest.Execute();
if (Commentresponse.Items.Count > 0)
{
video.title = Commentresponse.Items[0].Snippet.ChannelId;
video.description = Commentresponse.Items[0].Snippet.TextDisplay;
video.publishdate = Commentresponse.Items[0].Snippet.PublishedAt.Value;
}
else
{
//error
}
}
catch (Exception e)
{ }
}
In GoogleWebAuthorizationBroker.AuthorizeAsync change "user" to "admin".
This is a really late response but I had a similar issue. My project was using the YouTube API to upload videos to an account and in a separate section of the code was using it again to search for a video by ID to check its status.
My issue was I was using the same OAuth credentials to upload and then also for searching for a video.
This was failing because I had already set the YouTube Scope when uploading which was not the correct scope for searching for a video.
My simple solution for this was to create another set of OAuth credentials (Of type "Other") via the Google Developer Console, download the json file and use these details to obtain a different access token for the searching part of my code.
Hopefully this helps someone out.
I know this answer is a little late, but this is what fixed it for me. Hopefully it helps someone else.
I was receiving the same permissions denied error. After adding the YouTubeService.Scope.YoutubeForceSsl item to the scopes list, I was able to pull the comments.
Also, it looks like your code is not quite right. You will want to pull the CommentThreads based on the VideoId and include the replies (if you want them).
You won't be able to pull the comments for a video using Comments.
var threadsRequest = Client.CommentThreads.List("snippet,replies");
threadsRequest.VideoId = videoId;
var response = threadsRequest.Execute();
Same here.
Got it solved by specifying a the "dataStore" parameter in "GoogleWebAuthorizationBroker.AuthorizeAsync".
The service than wrote an acces-token JSON file into that folder.
Seems the storage option was required for authorization.
Also when authorization succeded, the browser jumped to an Google-authorisation URL where I had to logon and allow the levels of access I had requested from the API.
I used the API all the time before, but only for readonly ops.
Seems the "action" stuff (insert, update, delete, upload) requires more grants.
I am using oauth to get acces to google contacts from a desktop application. I have followed the instruction from google here: http://code.google.com/intl/iw-IL/apis/gdata/docs/auth/oauth.html#Examples but I am having problems
here is the code:
OAuthParameters parameters = new OAuthParameters()
{
ConsumerKey = CONSUMER_KEY,
ConsumerSecret = CONSUMER_SECRET,
Scope = SCOPE,
Callback = "http://localhost:10101/callback.htm.txt",
SignatureMethod = "HMAC-SHA1"
};
OAuthUtil.GetUnauthorizedRequestToken(parameters);
string authorizationUrl = OAuthUtil.CreateUserAuthorizationUrl(parameters);
Console.WriteLine(authorizationUrl);
var win = new GoogleAuthenticationWindow(authorizationUrl,parameters);
win.ShowDialog();
OAuthUtil.GetAccessToken(parameters);
inside the window I have the following:
private void BrowserNavigated(object sender, NavigationEventArgs e)
{
if (e.Uri.ToString().Contains("oauth_verifier="))
{
OAuthUtil.UpdateOAuthParametersFromCallback(e.Uri.ToString(), m_parameters);
Close();
}
}
at the last line (OAuthUtil.GetAccessToken(parameters);) I am getting a 400 bad request error and I have no idea why...
After much playing around... I think this is the easiest way to access google api:
Service service = new ContactsService("My Contacts Application");
service.setUserCredentials("mail#gmail.com", "password");
var token = service.QueryClientLoginToken();
service.SetAuthenticationToken(token);
var query = new ContactsQuery(#"https://www.google.com/m8/feeds/contacts/mail#gmail.com/full?max-results=25000");
var feed = (ContactsFeed)service.Query(query);
Console.WriteLine(feed.Entries.Count);
foreach (ContactEntry entry in feed.Entries)
{
Console.WriteLine(entry.Title.Text);
}
much easier than using oauth...