Note: If you don’t want to read my ramblings, and just want to access Google Drive from your ASP.NET Core application, you can just install the Pixata.Google Nuget package and follow the simple set-up instructions.
The rest of this post details (I know, too many details) my journey to that package and the pitfalls I encountered. This may be of interest if you enjoy reading about other people’s difficulties, or if you want to develop your own code. Caveat over, on with the ramble…
We are writing a case management system in Blazor, and as the client uses Google Drive extensively, we were asked to do the document storage there. This was a reasonable request, and having taken a quick look at Google’s API docs, I decided it shouldn’t be too difficult.
The main purpose of this blog post is because, believe it or not, just about everything you’ll read about accessing Google Drive from ASP.NET is either very misleading or just plain wrong! I can’t believe how much bad information there is out there, mostly from people who seem to have done a copy-and-paste job from the Google docs, without really checking to see if it worked.
Note that although I’m writing about Google Drive, the same should apply to pretty much any Google API
The other problem is that Google don’t seem to think that many people use .NET, so it’s not worth their while providing much in the way of docs or examples. Sure, there’s the .NET Quickstart, but that page will send you down a rabbit hole from which you may never return – unless you keep reading this blog post of course! Apart from that page, there’s very little in the way of help for .NET developers. Most of their pages only have examples in Java or Python (unless you’re into writing raw HTTP requests, which most of us aren’t).
The sad part is that they have missed the boat so badly that they aren’t even looking at Blazor. If you look in the Blazor support section of their backlog, they have a comment Action: keep an eye; if Blazor becomes very popular or we get multiple requests, we may revisit. I wonder if they have noticed how amazingly fast Blazor has been taken up? No, probably not, after all, it’s written by Microsoft! I’m sure if this were a Java technology, they would have been all over it by now.
Anyway, enough ranting, back to the issue at hand.
The first attempt – following the Quickstart
So, having looked around, and found that the Google Quickstart seemed to be the primary source for sample code, I whipped up a small Blazor project and tried it out. When I ran the project, I got a Google oAuth screen pop up, where I entered some credentials, and what do you know – it worked! Ooh I was chuffed.
At this point, I went off and developed a whole helper class for accessing Google Drive. This turned into a Nuget package, which I used in our main application. This all worked really well, and I prepared to give the client a demo on a staging server.
Funny how the most promising of days can end up so frustrating! I deployed the site to the staging server, and had a quick go before I showed the client. When I tried to access the first page that used Google Drive, I waited in anticipation for the oAuth screen… and waited, and waited, and… eventually got a time-out.
To cut a very long story slightly shorter, I eventually discovered that the code shown on the Quickstart only works for a desktop application. The reason for this is that it needs to interact with the desktop in order to pop up the browser window with the oAuth screen. When running on the server, it was running under the IIS user, who of course doesn’t have a desktop. The app was waiting for me to respond to a pop-up that never popped up.
What annoyed me (part 1) was that Google’s docs didn’t actually point this out. They mentioned that the sample was for a console app, but didn’t mention that it would only work in a console app. It was only when I found the following comment (spelling mistakes included!) on a GitHub issue that I realised why it was timing out…
GoogleWebAuthorizationBroker.AuthorizeAsync is for iinstalled applicatons. Its going to try and open the authorization web browser on the server which is not going to work.
Your going to need something like Web applications (ASP.NET MVC)
What annoyed me (part 2) was the number of blog posts that blindly copied the Google code from the Quickstart, claiming that they were showing you how to access Google Drive from ASP.NET, and quite obviously not having tried it when deployed to a real server. Sure it works in Visual Studio, because that (and therefore the IIS Express process it spawns) are running under your user account, so there is a desktop.
The second attempt – IGoogleAuthProvider
Whilst doing yet more searching around, I came across a GitHub repository for a sample ASP.NET MVC app that injected an instance of IGoogleAuthProvider into the controller, and used that to authorise the access to Google Drive.
I downloaded the sample and tried it out. All seemed fine, so I set about reworking the Nuget package code, as the code there wouldn’t have worked inside the helper class, it needed to be done in the main app. After some frustrating failures, I realised that it would have to be done in an MVC controller, not in a Blazor component, as (I think) it was trying to write to the response stream after it had been closed. This is similar (again I think) to the reason why ASP.NET Identity code doesn’t always work in Blazor, as it relies on writing to the response stream. As Blazor is an SPA, the response stream is closed when the app initially loads.
I had to add an MVC controller to the Blazor project, create the Drive service in the controller, and then pass it to the helper class. This made the usage a bit messy, and required some extra checks to make sure the service was initialised, but it did seem to work. Until…
As a precaution, I deployed the modified sample to IIS. Sadly, when I tried to authorise, it gave an exception from inside the Google API code.
Undeterred (OK, that’s a complete lie, I was fed up to the back teeth by this point!), I posted a question on StackOverflow, and did yet more searching. This turned up Linda Lawton’s web site. Linda is a certified Google Expert, one of the contributors to the the Google .NET client library, and the author of the sample project I was trying. I used her contact form to ask her about the sample code, pretty much at the same time as she commented on my SO question.
I took the opportunity of explaining my use case to her, and she pointed out that I was going about this wrong way altogether, and needed to use a service account (see below).
Because of this, I never found out why her sample threw the exception. However, if you want your app to access the user’s Google account, then this is the method you’ll have to use.
For me though, the conversation turned to other things…
The third and final attempt – a service account
I explained my requirements to Linda, and she pointed out that what I needed was a service account. This is designed for server-to-server use, and doesn’t require the end user to do any auth with Google. Ah ha, that’s a much better idea!
Predictably, there wasn’t any .NET code for this in Google’s docs, so it took a bit of fiddling and research to get something that worked, but after a surprisingly quick set of iterations, I had a sample working. I was able to access Google Drive without any exceptions.
The only problem was, I couldn’t see any of my files or folders! Every attempt to access files or folders that I could see clearly in the UI came back empty.
Back to Linda, who pointed out that when you create a service account, you’re actually creating a totally new Google user, and that user has their own Google Drive space. So, the reason I couldn’t see any of my files and folders is because I was looking in the wrong Drive space! She explained that all you need to do is share a folder in your own Drive space with the service account user, and you’re in business. She has a short video (12:46) that shows how to upload files to Google Drive using a service account, and this includes the bit about sharing the folder. She has another (even shorter) video (3:43) that shows you how to find the Id of the shared folder, which you’ll need to use as the root folder in your code.
Finally, I had what I wanted. The app was able to access Google Drive, and didn’t require any user auth at all.
So, in the end, my Nuget package (pretty much reverted back to where it was originally, with a slight modification for a service account) works very nicely, and the client is happy!
I hope this blog post saves someone the pain I went through to get to this point. If you need to access pretty much any of Google’s APIs, I strongly recommend you don’t waste time searching around. Read Linda’s site carefully, and you’ll find all you need. Once you’ve seen what she has to say, you can work out the rest from the Google docs. If you get stuck, ask her, she’s really helpful!