Wednesday, November 19, 2014

Universal Apps using the .NET client library for Google APIs

Hi all,

In the last couple weeks, Jesse and I improved the samples repository of the Google APIs client library for .NET with new samples for Windows and Windows Phone.
Jesse authored Translate.WP.TranslateText - a Windows Phone sample for the Google Translate API.
A week ago, I submitted a Blogger.Sample for Universal Store apps using the Google Blogger API.
These are the first samples in our repository that target Windows Phone 8.1 and Windows 8.1 apps using the .NET client library for Google APIs.

In this blog post I'll focus on the Universal Windows sample I created. The sample shares code between the Windows Store and Windows Phone versions, and uses MVVM best practices.

I must admit, the last time I wrote MVVM was back in 2011, back then I worked on a huge WPF project and while working on MVVM again I saw that nothing really has changed since then.

For those of you who don’t know, Universal Windows apps help you develop and share code between all Windows devices. You can read more about it in building-universal-windows-apps.
It's important to mention that I already had a hacky sample for Windows Phone 8.0 that I used to test a new release of the client library with it. I just thought that it is going to be easy to upgrade it to Windows Phone 8.1.

I WAS WRONG

I didn't find a right meme... any suggestion will be appreciated :)


Microsoft introduced significant breaking API changes when upgrading from 8.0 to 8.1. The WebAuthenticationBroker.AuthenticateAsync which I used in Windows Phone 8.0 isn't supported anymore. Starting with Windows 8.1, Microsoft supports several AndContinue methods (one of them is WebAuthenticationBroker.AuthenticateAndContinue). When you call an AndContinue method, the app is deactivated until the operation completes. You can read more in How to continue your Windows Phone Store app after calling an AndContinue method.

So, in order to support the OAuth 2.0 protocol for Windows Phone Store apps, I had to do the following:


Step 1

Create core classes that I’m going to move shortly to Google.Apis and Google.Apis.Auth new projects that will target Windows Phone 8.1. Those classes include the following:

  1. PasswordVaultDataStore
A Windows Phone implementation of IDataStore. It uses the platform PasswordVault. PasswordVault represents a credential locker and its content is private for each app and can’t be used by other apps or services in the phone.

  1. AuthorizationCodeBroker
An implementation of ICodeReceiver. The important method is ReceiveCodeAsync which should be called twice.
In the 1st time, the method calls WebAuthenticationBroker.AuthenticateAndContinue so that users would be able to authorize this app to access their personal resources.
In the 2nd time, it should be called after the app had already received the authorization code. The implementation counts on the developer to store the code response in the PasswordVaultDataStore using WebAuthResult.Name. (see changes in MainPage.xaml.cs later on…)

  1. AuthorizationCodeWPInstalledApp
An implementation of IAuthorizationCodeInstalledApp, that uses the standard AuthorizationCodeInstalledApp with the AuthorizationCodeBroker mentioned above.

  1. GoogleWebAuthorizationBroker
A helper class that puts everything together - the installed app and the password vault data store.


* All those classes are very similar to classes that we already have for supporting Windows Store applications or Windows Phone 8.0.


Step 2

In addition, each Windows Phone app that wants to support continuation should implement the following:

ContinuationManager

A custom and a very short implementation that targets authentication ONLY. The full implementation is available here: http://msdn.microsoft.com/en-us/library/dn631755.aspx. Take a look in this short version:
/// <summary>
/// ContinuationManager is used to detect if the most recent activation was due to a
/// authentication continuation.
///
/// Note: To keep this sample as simple as possible, the content of the file was changed
/// to support WebAuthenticationBrokerContinuation ONLY. Take a look in
/// http://msdn.microsoft.com/en-us/library/dn631755.aspx for a full documentation on how
/// to support continuation in other cases.
/// </summary>
public class ContinuationManager
{
internal void Continue(IContinuationActivatedEventArgs args)
{
switch (args.Kind)
{
case ActivationKind.WebAuthenticationBrokerContinuation:
var page = MainPage.Current as IWebAuthenticationContinuable;
if (page != null)
{
page.ContinueWebAuthentication(
args as WebAuthenticationBrokerContinuationEventArgs);
}
break;
}
}
}
App.xaml.cs
The application starting point. The important method is OnActivated, which might be called after the authorization code was received. The implementation looks like the following:
/// <summary>
/// On activated callback. It is used in order to continue the application
/// after the user authenticated.
/// </summary>
protected override void OnActivated(IActivatedEventArgs e)
{
base.OnActivated(e);
ContinuationManager = new Blogger.Common.ContinuationManager();
var continuationEventArgs = e as IContinuationActivatedEventArgs;
if (continuationEventArgs != null)
{
// Call ContinuationManager to handle continuation activation.
// The default ContinuationManager (which is documented in
// http://msdn.microsoft.com/en-us/library/dn631755.aspx) was changed to
/// handle ONLY authentication.
// It supports now current window and NOT current active frame.
ContinuationManager.Continue(continuationEventArgs);
}
Window.Current.Activate();
}
MainPage.xaml.cs
The first time the users log in to the app, the app should be suspended and the users must authorizes the app in order to access their private resources. When the app is activated again, it calls the continuation manager which in turn calls the ContinueWebAuthentication method. I think that the method is self documented, so without any words, take a look:
/// <summary>
/// Continues the app after retrieving the authorization code.
///
/// First it stores the authorization code result in the data store.
/// Then it calls the view model get blogs method, in order to retrieve all blogs.
/// The view model is responsible to check for if the authorization code exists in
/// the data store, and then, continue with the regular flow.
/// After retrieving all blogs, this method deletes the authorization code so it
/// won't be used in a next run.
/// </summary>
public async void ContinueWebAuthentication(
WebAuthenticationBrokerContinuationEventArgs args)
{
await PasswordVaultDataStore.Default.StoreAsync<WebAuthResult>(
WebAuthResult.Name, new WebAuthResult(args.WebAuthenticationResult));
await ((BlogsViewModel)this.DataContext).GetBlogsAsync();
await PasswordVaultDataStore.Default.DeleteAsync<WebAuthResult>(WebAuthResult.Name);
}


The only piece that is still missing to you now, is the BlogsViewModel and the BloggerRepository. Those two classes (and several others) are shared between the Windows and Windows Phone projects.
BlogsViewModel is just the ViewModel of the MainPage. When the users request to get all their blogs, the GetBlogsCommand is called. Then GetBlogsAsync will be executed and in turn it will call the repository to retrieve all blogs.
/// <summary>The blogs view model which contains all blog data.</summary>
public class BlogsViewModel : ViewModelBase
{
/// <summary>Gets the blog view models..</summary>
public ObservableCollection<BlogViewModel> Blogs { get; private set; }
private BlogViewModel selectedBlog;
/// <summary>Gets or sets the selected blog.</summary>
public BlogViewModel SelectedBlog
{
get { return selectedBlog; }
set
{
Set(() => SelectedBlog, ref selectedBlog, value);
if (selectedBlog != null)
selectedBlog.RefreshPosts();
}
}
private readonly IBloggerRepository repository;
/// <summary>Gets the get blogs command.</summary>
public RelayCommand GetBlogsCommand { get; private set; }
public BlogsViewModel(IBloggerRepository repository)
{
Blogs = new ObservableCollection<BlogViewModel>();
this.repository = repository;
this.GetBlogsCommand = new RelayCommand(
async () => await GetBlogsAsync());
}
/// <summary>Asynchronously gets all the blogs.</summary>
public async Task GetBlogsAsync()
{
var blogs = await repository.GetBlogsAsync();
// Fill the blogs collection should be from the
// thread that created the collection.
await UIUtils.InvokeFromUIThread(() =>
{
Blogs.Clear();
foreach (var b in blogs)
{
Blogs.Add(new BlogViewModel(repository)
{
Name = b.Name,
Id = b.Id
});
}
});
}
}
view raw BlogsViewModel hosted with ❤ by GitHub
And the BloggerRepository looks like the following:
* Note that the implementation of GoogleWebAuthorizationBroker is different for Windows and Windows Phone apps.
/// <summary>
/// The blogger repository implementation which works the same for Windows
/// and Windows Phone.
/// </summary>
public class BloggerRepository : IBloggerRepository
{
private UserCredential credential;
private BloggerService service;
private async Task AuthenticateAsync()
{
if (service != null)
{
// The user had already authenticated.
return;
}
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
new Uri("ms-appx:///Assets/client_secrets.json"),
new[] { BloggerService.Scope.BloggerReadonly },
"user",
CancellationToken.None);
var initializer = new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "BloggerApp",
};
service = new BloggerService(initializer);
}
public async Task<IEnumerable<Blog>> GetBlogsAsync()
{
await AuthenticateAsync();
var list = await service.Blogs.ListByUser("self").ExecuteAsync();
return from blog in list.Items
select new Blog
{
Id = blog.Id,
Name = blog.Name
};
}
public async Task<IEnumerable<Post>> GetPostsAsync(string blogId)
{
await AuthenticateAsync();
var list = await service.Posts.List(blogId).ExecuteAsync();
return from post in list.Items
select new Post
{
Title = post.Title,
Content = post.Content
};
}
}
Take a look in the code sample at Blogger.Sample. You will find all the missing pieces there, like IBloggerRepository, BlogViewModel, PostViewModel, MainPage and other classes. Just remember to follow the README file in order to setup and run the app.


Upstate NY, Oct 2014


Stay tuned for further documentation - the Windows Phone 8.1 section is going to be refreshed soon.



We hope you find our new sample useful when writing Windows Phone 8.1 apps with Google Apis. Let us know if you have any questions in the comments of this post, on the client issue tracker or in Stack Overflow using the google-api-dotnet-client tag (Preferred).



Enjoy!
Eyal (upside down)

Sunday, August 3, 2014

Musical artist events rich list

Exactly 4 weeks have passed since I came back from Brazil!
It was one of the best trip EVER. The world cup was amazing and the atmosphere was so magnificent! Many football (or should I say soccer...) fans from all over the world gathered together in the land of football (Yes, Although the 7-1, Brazil was and it's still the land of football :))

6 games, 15 goals, many games in Copacabana's fan fest, beers and caipirinhas with Brazilians, Hollandians, Germans, Argentinians, and as a matter of fact, with who not :)
It was a CRAZY FESTIVAL!
Holland(5) - Spain(1), what an amazing game to start with! Salvador, June 2014
Belgium(1) - Russia(0). Maracana, Rio de Janeiro, June 2014 

And back to work...

It's time to update you about my contribution to Google Search. Besides developing the Google APIs client library for .NET, I have been working in the last months on several search features. One of the things that I'm really proud of, is the artist events rich list.
Just "Google" [lada gaga events][christina perri tour][piano guys concerts] and a cool rich list with all upcoming events is going to be displayed, as following:



As you can see, first we display the nearby events; Google already knows that I'm living in NYC, so events that are located close to me are being displayed first, and then all the others, sorted by date.

Take a look in getting your events to the knowledge graph video by my Google fellow, Justin Boyan.



And what about the Google APIs client library for .NET you are asking?
I'm working on targeting Windows 8.1 as well as all the other platforms we already support. Version 1.9.0 is coming soon...

Eyal

Sunday, June 1, 2014

1.8.2 is here :)

Hi all,
Several days ago I released a new version of the core Google APIs client library for .NET (version 1.8.2)

This release includes several bug fixes, such as fixing MaxUrlLengthInterceptor to not delete the last character of a URL and improving FileDataStore to support an absolute path folder.

The full issues list is available in the library's latest blogpost.

In addition, from this release we support token revocationUserCredential now contains a new RevokeTokenAsync method which revokes the current token.

Later on, you can reauthorize the user again by calling GoogleWebAuthorizationBroker's ReauthorizeAsync. By doing so, the user will have to reauthorize your application to use his or her private resources.

Take a look in the following snippet:

UserCredential credential;
using (var stream = new FileStream("client_secrets.json",
FileMode.Open,
FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
new[] { BooksService.Scope.Books },
"user", CancellationToken.None);
}
// Create the service.
var service = new BooksService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Token Revocation Sample",
});
// List my library bookshelves - This request will succeed
var bookselve = await service.Mylibrary.Bookshelves.List().ExecuteAsync();
// Revoke the credential.
await credential.RevokeTokenAsync(CancellationToken.None);
// Request will fail now - invalid grant.
try
{
bookselve = await service.Mylibrary.Bookshelves.List().ExecuteAsync();
}
catch (TokenResponseException ex)
{
Console.WriteLine(ex.Error);
}
// Reauthorize the user. A browser will be opened, and the user will enter
// his or her credential again.
await GoogleWebAuthorizationBroker.ReauthorizeAsync(
credential, CancellationToken.None);
// The request will succeed now.
bookselve = await service.Mylibrary.Bookshelves.List().ExecuteAsync();
view raw TokenRevocation hosted with ❤ by GitHub
Note the following:
  1. You should already be familiar with lines 1-20. Those lines contain the creation of user credentials and a book service, and a sample request to this service.
  2. In line 23 there is a call to the new RovokeTokenAsync method. From this point the access token is invalid and it is removed form the data store.
  3. Then, any request using the current credentials will fail, because the access token was revoked (lines 25-33).
  4. In this point, the user can reauthorize the application again to access his or her private resources, by calling to ReauthorizeAsync (line 37).

While working on this release I encountered the following error over and over again while trying to compile a Windows Phone sample using the new 1.8.2 library:


Error 3 Cannot resolve reference assemblies. Please check the reference assemblies. Cannot resolve dependency to assembly 'Google.Apis, Version=1.8.1.31687, Culture=neutral, PublicKeyToken=null' because it has not been preloaded. When using the ReflectionOnly APIs, dependent assemblies must be pre-loaded or loaded on demand through the ReflectionOnlyAssemblyResolve event.

It took me a lot of time to understand what is the exact problem, and I finally found a solution in this blogpost: http://www.ideatoappster.com/how-to-overcome-windows-phone-8-xaml-compilation-errors/.
Thanks Mr. Gill :) 
All I had to do is to change the following line in the Windows Phone project file:
<ValidateXaml>true</ValidateXaml>
And set the validate XAML tag to false.
That's all.


SF view from the Golden Gate Bridge, May 2014

The countdown continues... In 10 days I'm going to arrive in Brazil and in 12 days I'm going to watch my first world cup game ever. And... what an amazing game to start with: Spain-Holland.
Bom dia amigos!

Saturday, March 29, 2014

Mission Accomplished

I'm really proud that the Google APIs client library for .NET had reached GA.
It's something that I worked on in my last year, and I'm so happy that this awesome library is finally GA.

Read more about the GA announcements in the following links:

I recommend all of you to visit our Getting Started page and also OAuth 2.0, Media Upload and Media Download, Batch Requests, Performance Tips, and also the APIs page which includes all the supported Google APIs.

Jay Leno and I, on the day of the announcement, March 2014 :)
As you can see, Jay Leno is a big fan of the library :)

And to be serious again,
I know that I still owe you an EF implementation of IDataStore.
As I'm currently working on another big feature of the client library, I think I'll postpone it for later on. Sorry...

Eyal

Wednesday, January 29, 2014

ASP.NET MVC With Google OpenID and OAuth 2.0

Several days ago I encountered Rick Anderson's great Create an ASP.NET MVC 5 App with Facebook and Google OAuth2 and OpenID Sign-on post.
While reading the blog post, it became apparent to me that there should be a similar example showing how to Sign-in with Google and access Google APIs within an MVC 5 application. I chose to write a simple sample to display the user's files form his or her Google Drive account using Google Drive API.

* All the code of this tutorial is available in https://github.com/peleyal/peleyal.git.

* UPDATE (Dec 2014) *
Google OpenID is deprecated please use OAuth 2.0 for accessing Google APIs.
More information about migrating OpenID to OAuth 2.0 is available at: https://developers.google.com/+/api/auth-migration.

Prerequisite:

The pre-requisites for this tutorial are the same as those in Rick's example.
First, install Visual Studio Express 2013 for Web or Visual Studio 2013.
Next, create a new ASP.NET MVC application (I chose to call my application GoogleApis.Sample.MVC) with the default individual user account.
Continue as described in the article. Make sure to uncomment the following line:  app.UseGoogleAuthentication();
It's located in ConfigureAuth method in App_Start\Startup.Auth.cs file)

Let's start:

1. Install NuGet packages



2. OAuth 2.0

Follow the OAuth 2.0 page for ASP.NET MVC applications. I'll repeat the key instructions in this section.

2.1 Create a new AppAuthFlowMetadata that inherits from FlowMetadata.

In my sample I added the following class to Controllers folder.

using System;
using System.Web.Mvc;
using Microsoft.AspNet.Identity;

using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Mvc;
using Google.Apis.Drive.v2;
using Google.Apis.Util.Store;

namespace Google.Apis.Sample.MVC.Controllers
{
    public class AppAuthFlowMetadata : FlowMetadata
    {
        private static readonly IAuthorizationCodeFlow flow =
            new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
                {
                    ClientSecrets = new ClientSecrets
                    {
                        ClientId = "PUT_CLIENT_ID_HERE",
                        ClientSecret = "PUT_CLIENT_SECRET_HERE"
                    },
                    Scopes = new[] { DriveService.Scope.Drive },
                    DataStore = new FileDataStore("Google.Apis.Sample.MVC")
                });

        public override string GetUserId(Controller controller)
        {
            return controller.User.Identity.GetUserName();
        }

        public override IAuthorizationCodeFlow Flow
        {
            get { return flow; }
        }
    }
}

  • Make sure to replace the ClientID and ClientSecret values with the client secrets from your Google Developers Cloud project.
  • Note: In my next blogpost I'll share a new implementation of EFDataStore to add support for Entity Framework as a storage mechanism.
  • I used the Microsoft.AspNew.Identity.GetUserName() extension method to get the user identity. Because I've configured the application to use the Google flow, the identity returned from this object corresponds to the user who authenticated to this site.

2.2 Create your Auth callback


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Google.Apis.Sample.MVC.Controllers
{
    public class AuthCallbackController
            Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
    {
        protected override Google.Apis.Auth.OAuth2.Mvc.FlowMetadata FlowData
        {
            get { return new AppAuthFlowMetadata(); }
        }
    }
}

Later on, I'll create the service object for accessing the Google Drive API.

2.3 Register the Auth address into your Cloud project

Get the project URL from your project file inside VS2013. Then register that URI in your Google Developers Console project.



As you can see in the above images, I registered http://localhost:49244/AuthCallback/IndexAsync as a Redirect URI to my cloud project (Visual Studio picked 49244 port to my web application).

Make sure you also enable the Drive API in the APIs tab.

3. Get files from the DriveService

In this step we will create a DriveService instance and fetch all the user's files.

3.1 Create a model for Drive's File

Before we can retrieve information about the user's Drive files, we should specify a "local" model (It's just a best practice).

using System;

namespace Google.Apis.Sample.MVC.Models
{
    public class FileModel
    {
        public string Id { get; set; }
        public string Title getset; }
        public DateTime? CreatedDate getset; }
        public string DownloadUrl getset; }
    }
}

3.2 Add DriveAsync method to the HomeController

Start by adding the following using statements:

using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;

using Google.Apis.Sample.MVC.Models;
using Google.Apis.Auth.OAuth2.Mvc;
using Google.Apis.Download;
using Google.Apis.Drive.v2;
using Google.Apis.Services;

Next, add the following code to make an API call to Google Drive:

    [Authorize]
    public async Task<ActionResult> DriveAsync(CancellationToken cancellationToken)
    {
        ViewBag.Message = "Your drive page.";

        var result = await new AuthorizationCodeMvcApp(this, new AppAuthFlowMetadata()).
                AuthorizeAsync(cancellationToken);

        if (result.Credential == null)
            return new RedirectResult(result.RedirectUri);

        var driveService = new DriveService(new BaseClientService.Initializer
            {
                HttpClientInitializer = result.Credential,
                ApplicationName = "ASP.NET Google APIs MVC Sample"
            });

        var listReq = driveService.Files.List();
        listReq.Fields = "items/title,items/id,items/createdDate,items/downloadUrl,items/exportLinks";
        var list = await listReq.ExecuteAsync();
        var items = 
            (from file in list.Items
             select new FileModel
             {
                  Title = file.Title,
                  Id = file.Id,
                  CreatedDate = file.CreatedDate,
                  DownloadUrl = file.DownloadUrl ?? 
                                            (file.ExportLinks != null ? file.ExportLinks["application/pdf"] : null),
             }).OrderBy(f => f.Title).ToList();
        return View(items);
    }

  • Notice that the DriveAsync operation is marked with the Authorize attribute. In this case we use this attribute because we require users to log in before they granted access to this content.
  • The result of AuthorizationCodeMvcApp.AuthorizeAsync is an AuthResult. The RedirectUri property is set in case the end-user needs to authorize. Otherwise the Credential property will be set with the user's credential.
  • Then I create the service and it's list request. Notice that I set the Fields property of the request to fetch only the fields that I'm going to use in the view.
  • I convert the Drive API file model into my sample model, order the list by title, and send it to the client. 

4. UI changes

4.1 Add a new DriveAsync view under Views/Home


@model IEnumerable<Google.Apis.Sample.MVC.Models.FileModel>

@{
    ViewBag.Title = "Drive";
}

<table>
    <tr>
        <th>
            Title
        </th>
        <th>
            Created Date
        </th>
        <th>
        </th>
    </tr>
    @foreach (var file in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => file.Title)
            </td>
            <td>
                @Html.DisplayFor(modelItem => file.CreatedDate)
            </td>
            <td>
                @Html.ActionLink("Download", "DownloadAsync", new 
                     { 
                         title = file.Title,
                         downloadUrl = file.DownloadUrl 
                     })
            </td>
        </tr>
    }
</table>

4.2 Change _Layout.cshtml to include a link to the new page


                    <li>@Html.ActionLink("Home", "Index", "Home")</li>
                    <li>@Html.ActionLink("About", "About", "Home")</li>
                    <li>@Html.ActionLink("Drive", "DriveAsync", "Home")</li>
                    <li>@Html.ActionLink("Contact", "Contact", "Home")</li>

Add only the highlighted line. All the rest lines of code are already exist in the file.

5. Run


The first page you are going to see when running the web application is the following:


To sign in, click on the Drive tab. The following screen shows he login page.


Choose Google as a service login.


Now you should login.


Approve the site to use the required scopes to view your email address and basic information.


Choose a user name.


The above screen might look weird to you.
You already approved the application to use your email and basic information, so you might wonder why do you need to approve again other scopes?

The reason you need to approve the other scopes when the user sign in the first time is to authenticate the user. In the above screen the user needs to authorize this site to access his or her Drive data.

We might want to avoid those duplicate steps in the future and try making both of them as a one step, but I'll leave it to the future.


We are DONE!

6. Coming Next
  • Download a file using MediaDownloader
  • Implement a new EFDataStore - Entity Framework implementation of IDataStore.
  • Any suggestions?

And I know that you are curious about the photo of this post... It's Prague! And unfortunately not in a sunny day.

Prague Oct 2009