First of all let me clarify that I'm not familiar with the Google Drive API. I've done some refactoring but I haven't tested it (just make it compile).
If I can assume you are using greater C# version than 8 then you can refactor your code like this:
using static {YourMainNamespace}.Constants;
class Program
{
static async Task Main(string[] args)
{
UserCredential credential = await AuthorizeAgainstGoogleDriveAsync();
IList<Google.Apis.Drive.v3.Data.File> files = await GetFilesMetaInfoAsync(credential);
DisplayFilesMetaInfoInTabularFashion(files);
Console.ReadLine();
}
static async Task<UserCredential> AuthorizeAgainstGoogleDriveAsync(
string sourceRelativePath = Authorize.RelativeFilePath.Source,
string targetRelativePath = Authorize.RelativeFilePath.Target)
{
using var stream = new FileStream(sourceRelativePath, FileMode.Open, FileAccess.Read);
var credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Authorize.Scopes,
Authorize.User,
CancellationToken.None,
new FileDataStore(targetRelativePath, true));
Console.WriteLine($"Credential file saved to: {Authorize.RelativeFilePath.Target}");
return credential;
}
static async Task<IList<Google.Apis.Drive.v3.Data.File>> GetFilesMetaInfoAsync(
UserCredential credential,
string applicationName = GetFiles.ApplicationName,
int pageSize = GetFiles.PageSize,
string fields = GetFiles.Fields)
{
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = applicationName,
});
FilesResource.ListRequest listRequest = service.Files.List();
listRequest.PageSize = pageSize;
listRequest.Fields = fields;
var fileList = await listRequest.ExecuteAsync();
return fileList.Files;
}
static void DisplayFilesMetaInfoInTabularFashion(IList<Google.Apis.Drive.v3.Data.File> files)
{
Console.WriteLine("Files:");
Console.WriteLine($"{DisplayFiles.Name}{new string('\t', 6)}{DisplayFiles.Id}{new string('\t', 8)}{DisplayFiles.ModifiedTime}");
if (files == null || files.Count <= 0)
{
Console.WriteLine(DisplayFiles.NoFileMessage);
return;
}
foreach (var file in files)
{
Console.WriteLine($"{file.Name,-56}{file.Id,-36}{file.ModifiedTime}");
}
}
}
static class Constans
{
public static class Authorize
{
public const string User = "user";
public readonly static string[] Scopes = new[] { DriveService.Scope.DriveReadonly };
public static class RelativeFilePath
{
public const string Source = "credentials.json";
public const string Target = "token.json";
}
}
public static class GetFiles
{
public const string ApplicationName = "GoogleDriveFileManager";
public const int PageSize = 1000;
public const string Fields = "nextPageToken, files(id, name)";
}
public static class DisplayFiles
{
private const string prefix = "file.";
public const string Name = prefix + nameof(Name);
public const string Id = prefix + nameof(Id);
public const string ModifiedTime = prefix + nameof(ModifiedTime);
public const string NoFileMessage = "No files found.";
}
}
Let me give you some context for each part:
Main
static async Task Main(string[] args)
{
UserCredential credential = await AuthorizeAgainstGoogleDriveAsync();
IList<Google.Apis.Drive.v3.Data.File> files = await GetFilesMetaInfoAsync(credential);
DisplayFilesMetaInfoInTabularFashion(files);
Console.ReadLine();
}
- I've made your
Main
async (supported since C# 7.1) to be able to use async
-await
- I've split your logic into three smaller functions to make your
Main
short and concise
- If the scope of the application is this small then you can use shorter names of course, like
AuthorizeAsync
GetFiles
DisplayFiles
Constants
using static {YourMainNamespace}.Constants;
...
static class Constans
{
public static class Authorize
{
public const string User = "user";
public readonly static ImmutableArray<string> Scopes = new[] { DriveService.Scope.DriveReadonly }.ToImmutableArray();
public static class RelativeFilePath
{
public const string Source = "credentials.json";
public const string Target = "token.json";
}
}
public static class GetFiles
{
public const string ApplicationName = "GoogleDriveFileManager";
public const int PageSize = 1000;
public const string Fields = "nextPageToken, files(id, name)";
}
public static class DisplayFiles
{
private const string prefix = "file.";
public const string Name = prefix + nameof(Name);
public const string Id = prefix + nameof(Id);
public const string ModifiedTime = prefix + nameof(ModifiedTime);
public const string NoFileMessage = "No files found.";
}
}
- I've put all of the hardcoded values into a hierarchical structure
- Because I'm unfamiliar with the domain I've organize them based on the usage
- In normal cases you should organize them based on their semantics
- I've used
const
(which is implicit static
) to prevent modification
- In case of
Scope
I've used ImmutableArray
to prevent modification
- Here I can't use
const
but it could (and should) be marked as readonly
- I've used
using static
to avoid prefixing with Constants
each and every time
AuthorizeAgainstGoogleDriveAsync
static async Task<UserCredential> AuthorizeAgainstGoogleDriveAsync(
string sourceRelativePath = Authorize.RelativeFilePath.Source,
string targetRelativePath = Authorize.RelativeFilePath.Target)
{
using var stream = new FileStream(sourceRelativePath, FileMode.Open, FileAccess.Read);
var credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Authorize.Scopes,
Authorize.User,
CancellationToken.None,
new FileDataStore(targetRelativePath, true));
Console.WriteLine($"Credential file saved to: {Authorize.RelativeFilePath.Target}");
return credential;
}
- I've made the source and target file paths configurable (but with default values)
- If you wish you can make the same with user and scopes
- One can argue whether the
Console.WriteLine
should belong here or inside the Main
- I've put it here to make the main as concise as possible
- But from reusability point of view it should not belong here
- I've used using declaration (which is a C# 8 feature) to avoid the block operator (
{ ... }
)
GetFilesMetaInfoAsync
static async Task<IList<Google.Apis.Drive.v3.Data.File>> GetFilesMetaInfoAsync(
UserCredential credential,
string applicationName = GetFiles.ApplicationName,
int pageSize = GetFiles.PageSize,
string fields = GetFiles.Fields)
{
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = applicationName,
});
FilesResource.ListRequest listRequest = service.Files.List();
listRequest.PageSize = pageSize;
listRequest.Fields = fields;
var fileList = await listRequest.ExecuteAsync();
return fileList.Files;
}
- I've made this API call to async (
Execute
>> ExecuteAsync
) to make use of the async non-blocking I/O
- I've declared the function with three optional parameters to be able to customize the request if needed
- I've realized that you are not making use of the
nextPageToken
but I did not remove it
- I've assumed that the implementation of paging will be your next step
DisplayFilesMetaInfoInTabularFashion
static void DisplayFilesMetaInfoInTabularFashion(IList<Google.Apis.Drive.v3.Data.File> files)
{
Console.WriteLine("Files:");
Console.WriteLine($"{DisplayFiles.Name}{new string('\t', 6)}{DisplayFiles.Id}{new string('\t', 8)}{DisplayFiles.ModifiedTime}");
if (files == null || files.Count <= 0)
{
Console.WriteLine(DisplayFiles.NoFileMessage);
return;
}
foreach (var file in files)
{
Console.WriteLine($"{file.Name,-56}{file.Id,-36}{file.ModifiedTime}");
}
}
- This operation is tightly coupled to the request
- It highly relies on the retrieved
fields
- So, it might make sense to it make more dynamic if needed
- I've used
new string
instead of new String
- I've reverted the
if
statement to use the early exit pattern
- With this the main logic (
foreach
) is not indented
- I've also used the capabilities of string interpolation to avoid calls like
PadRight
async main
is supported. \$\endgroup\$