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
- Install the Google.Apis.Drive prerelease NuGet package.
- Install the Google.Apis.Auth.MVC prerelease NuGet package.
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
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.
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 class FileModel
{
public string Id { get; set; }
public string Title { get; set; }
public DateTime? CreatedDate { get; set; }
public string DownloadUrl { get; set; }
}
}
}
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
(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),
(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>
</th>
</th>
</tr>
{
<tr>
<td>
@Html.DisplayFor(modelItem => file.Title)
</td>
</td>
{
title = file.Title,
downloadUrl = file.DownloadUrl
})
</td>
}
</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 |