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.
Ha ha.
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!
I read this with interest as i am nearly bald from tearing my hair out trying to do the same. I am a journeyman coder at best and its not my dayjob so it takes me an inordinate amunt of time to figure thiese things out.
Would you be able to share a vanilla bit of code that i can experiement with?
thanks
Hi John.
Did you look at the first link in my post (https://github.com/MrYossu/Pixata.Utilities/tree/master/Pixata.Google)? That has sample code that should get you going.
If you’re still confused, please open an issue in the GitHub repro (https://github.com/MrYossu/Pixata.Utilities/issues/new) and explain how far you got and where you’re stuck, and I’ll see if I can help. Could be that my sample code isn’t clear enough, in which case your feedback will help me improve it.
Thanks,
Avrohom Yisroel
Interestingly enough, my experience developing for OneDrive wasn’t any better, if not even worse… There, the first task was to actually discover what version I got with Office 365 – is it OneDrive personal or for business? I went halfway through the implementation to discover that I have neither but some version of Sharepoint – although still called OneDrive. Now, there’s at least four ways to access it from .Net, using plain REST calls, using libraries called CSOM and PNP, and using Microsoft Graph. Neither of which, as far as I’ve seen, support the full OneDrive functionality but you discover what bits are missing along the way – or even at the end of it as I did. You can’t just share a folder and upload to it like you do from the browser: or you supposedly can with Microsoft Graph but that feature isn’t complete yet (or is it? It certainly seems undocumented). Using a secret key for access is currently becoming obsolete but works if you know where to look, and the preferred way for authentication is using certificates but I was unable to find what kind they were supposed to be (there’s a powershell script that generates a self-signed one and I just didn’t want to go down that rabbit hole). Controlling access to OneDrive has moved to Azure AD which isn’t even called that anymore. And, once you manage to skip all those obstacles (I’d say Microsoft provided as many obstacles as there are solutions) and get a working application, you find out that the OneDrive/Sharepoint has a built in DoS, that is – if it gets the impression that you’re sending too much requests, it will just plain deny the service and return a 50x error. And the PNP library seems to do just that, uploading small files works but returns errors for large ones. At that point I gave up because having to implement some kind of throttling logic (and that’s what they suggest on the support forums) to achieve what can be done with a drag-drop from the browser is just insane.
So, if you ever thought of replacing Google Drive with OneDrive for this purpose: just don’t.
Oh dear, I’m glad I used Google Drive then!
Truth is, without Linda’s help, I’m not sure how far I would have got with Google either, their docs are very complex and not particularly clear.