Sometime ago, I wrote this article for the MSDN Magazine, about Aspect Oriented Programming and how it could solve cross-cutting concerns in your application, like:

  • Authentication
  • Logging
  • Data audit
  • Data validation
  • Data caching
  • Performance measuring

The article shows how to use the Decorator pattern and the RealProxy class to create a Dynamic Proxy to solve these issues in a simple manner. If you want to have a full introduction on the subject, I suggest that you take a look at the article.
The time has passed, things changed a lot and we are now close to the introduction of .NET 7. The RealProxy class doesn’t exist anymore, as it’s based in Remoting, which was not ported to .NET Core. Fortunately, we still have the System.Reflection.DispatchProxy class that can solve the problem.

With this class, we can still write proxies that decorate our classes and allow us to implement AOP in our programs. In this article, we will use the DispatchProxy class to create a dynamic proxy that allows us to implement a filter for the methods to be executed and execute other functions before and after the method execution.

In the command prompt, create a new console app with:

dotnet new console -o DynamicProxy
cd DynamicProxy
code .

In Program.cs, we will define a Customer record (put it at the end of the code):

record Customer(string Id, string Name, string Address);

Then, add a new Repository.cs file and add an IRepository interface in it:

public interface IRepository<T>
{
    void Add(T entity);
    void Delete(T entity);
    IEnumerable<T> GetAll();
}

The next step is to create the generic class Repository that implements this interface:

public class Repository<T> : IRepository<T> 
{
    private readonly List<T> _entities = new List<T>();
    public void Add(T entity)
    {
        _entities.Add(entity);
        Console.WriteLine("Adding {0}", entity);
    }

    public void Delete(T entity)
    {
        _entities.Remove(entity);
        Console.WriteLine("Deleting {0}", entity);
    }

    public IEnumerable<T> GetAll()
    {
        Console.WriteLine("Getting entities");
        foreach (var entity in _entities)
        {
            Console.WriteLine($"  {entity}");
        }
        return _entities;
    }
}

As you can see, our repository class is a simple class that will store the entities in a list, delete and retrieve them.

With this class created, we can add the code to use it in Program.cs:

Console.WriteLine("***\r\n Begin program\r\n");
var customerRepository = new Repository<Customer>();
var customer = new Customer(1, "John Doe", "1 Main Street");
customerRepository.Add(customer);
customerRepository.GetAll();
customerRepository.Delete(customer);
customerRepository.GetAll();
Console.WriteLine("\r\nEnd program\r\n***");

If you run this program, you will see something like this:

Now, let’s say we want to implement logging to this class, and have a log entry for every time it enters a method and another entry when it exits. We could do that manually, but it would be cumbersome to add logging before and after every method.

Using the DispatchProxy class, we can implement a proxy that will add logging to any class that implements the IRepository interface. Create a new file RepositoryLoggerProxy.cs and add this code:

using System.Reflection;

class RepositoryLogger<T> : DispatchProxy where T : class
{
    T? _decorated;

    public T? Create(T decorated)
    {
        var proxy = Create<T, RepositoryLogger<T>>() as RepositoryLogger<T>;
        if (proxy != null)
        {
            proxy._decorated = decorated;
        }
        return proxy as T;
    }


    protected override object? Invoke(MethodInfo? methodInfo, object?[]? args)
    {
        if (methodInfo == null)
        {
            return null;
        }

        Log($"Entering {methodInfo.Name}");
        try
        {
            var result = methodInfo.Invoke(_decorated, args);
            Log($"Exiting {methodInfo.Name}");
            return result;
        }
        catch
        {
            Log($"Error {methodInfo.Name}");
            throw;
        }
    }

    private static void Log(string msg)
    {
        Console.ForegroundColor = msg.StartsWith("Entering") ? ConsoleColor.Blue :
            msg.StartsWith("Exiting") ? ConsoleColor.Green : ConsoleColor.Red;
        Console.WriteLine(msg);
        Console.ResetColor();
    }
}

The RepositoryLogger class inherits from DispatchProxy and has a Create method that will create an instance of a class that implements the interface that’s decorated. When we call the methods of this class, they are intercepted by the overriden Invoke method and we can add the logging before and after executing the method.

To use this new class, we can use something like:

Console.WriteLine("***\r\n Begin program\r\n");
var customerRepository = new Repository<Customer>();
var customerRepositoryLogger = new RepositoryLogger<IRepository<Customer>>().Create(customerRepository);
if (customerRepositoryLogger == null)
{
    return;
}
var customer = new Customer(1, "John Doe", "1 Main Street");
customerRepositoryLogger.Add(customer);
customerRepositoryLogger.GetAll();
customerRepositoryLogger.Delete(customer);
customerRepositoryLogger.GetAll();
Console.WriteLine("\r\nEnd program\r\n***");

Now, running the code, we get:

We have logging entering and exiting the class without having to change it. Remove logging is as simple as changing one line of code.

With this knowledge, we can extend our proxy class to do any action we want. To add actions before, after and on error is just a matter of passing them in the creation of the proxy. We can create a DynamicProxy class with this code:

using System.Reflection;

class DynamicProxy<T> : DispatchProxy where T : class
{
    T? _decorated;
    private Action<MethodInfo>? _beforeExecute;
    private Action<MethodInfo>? _afterExecute;
    private Action<MethodInfo>? _onError;
    private Predicate<MethodInfo> _shouldExecute;

    public T? Create(T decorated, Action<MethodInfo>? beforeExecute, 
        Action<MethodInfo>? afterExecute, Action<MethodInfo>? onError, 
        Predicate<MethodInfo>? shouldExecute)
    {
        var proxy = Create<T, DynamicProxy<T>>() as DynamicProxy<T>;
        if (proxy == null)
        {
            return null;
        }
        proxy._decorated = decorated;
        proxy._beforeExecute = beforeExecute;
        proxy._afterExecute = afterExecute;
        proxy._onError = onError;
        proxy._shouldExecute = shouldExecute ?? (s => true);
        return proxy as T;
    }

    protected override object? Invoke(MethodInfo? methodInfo, object?[]? args)
    {
        if (methodInfo == null)
        {
            return null;
        }
        if (!_shouldExecute(methodInfo))
        {
            return null;
        }
        _beforeExecute?.Invoke(methodInfo);
        try
        {
            var result = methodInfo.Invoke(_decorated, args);
            _afterExecute?.Invoke(methodInfo);
            return result;
        }
        catch
        {
            _onError?.Invoke(methodInfo);
            throw;
        }
    }
}

In the Create method, we pass the actions we want to execute before after and on error after each method. We can also pass a predicate to filter the methods we don’t want to execute. To use this new class, we can do something like this:

Console.WriteLine("***\r\n Begin program\r\n");
var customerRepository = new Repository<Customer>();
var customerRepositoryLogger = new DynamicProxy<IRepository<Customer>>().Create(customerRepository,
    s => Log($"Entering {s.Name}"),
    s => Log($"Exiting {s.Name}"),
    s => Log($"Error {s.Name}"),
    s => s.Name != "GetAll");
if (customerRepositoryLogger == null)
{
    return;
}
var customer = new Customer(1, "John Doe", "1 Main Street");
customerRepositoryLogger.Add(customer);
customerRepositoryLogger.GetAll();
customerRepositoryLogger.Delete(customer);
customerRepositoryLogger.GetAll();
Console.WriteLine("\r\nEnd program\r\n***");

static void Log(string msg)
{
    Console.ForegroundColor = msg.StartsWith("Entering") ? ConsoleColor.Blue :
        msg.StartsWith("Exiting") ? ConsoleColor.Green : ConsoleColor.Red;
    Console.WriteLine(msg);
    Console.ResetColor();
}

Executing this code will show something like:

Note that, with this code, the method GetAll isn’t executed, as it was filtered by the predicate.

As you can see, this is a very powerful class, as it can implement many different aspects for any interface (the DispatchProxy class only works with interfaces). For example, if I want to create my own mocking framework, where I don’t execute any method of a class, I can change the code of the Invoke method to

protected override object? Invoke(MethodInfo? methodInfo, object?[]? args)
{
    if (methodInfo == null)
    {
        return null;
    }
    _beforeExecute?.Invoke(methodInfo);
    try
    {
        object? result = null;
        if (_shouldExecute(methodInfo))
        {
            result = methodInfo.Invoke(_decorated, args);
        }
        _afterExecute?.Invoke(methodInfo);
        return result;
    }
    catch
    {
        _onError?.Invoke(methodInfo);
        throw;
    }
}

And create the proxy with something like this:

var customerRepositoryLogger = new DynamicProxy<IRepository<Customer>>().Create(customerRepository,
    s => Log($"Entering {s.Name}"),
    s => Log($"Exiting {s.Name}"),
    s => Log($"Error {s.Name}"),
    s => false);

In this case, the real functions won’t be called, just the methods before and after the call:

As you can see, the DispatchProxy class allows the creation of powerful classes that add aspects to your existing classes, without having to change them. With the DynamicProxy class you just have to add the actions to execute and the filter for the functions to be executed.

All the source code for this article is at https://github.com/bsonnino/DynamicProxy

When you are developing a new project and need to store settings for it, the first thing that comes to mind is to use the Appsettings.json file. With this file, you can store all settings in a single file and restore them easily.

For example, let’s create a console project that has three settings: Id, Name and Version. In a command line prompt, type:

dotnet new console -o SecretStorage
cd SecretStorage
code .

This will open VS Code in the folder for the new project. Add a new file named appSettings.json and add this code in it:

{
    "AppData": {
        "Id": "IdOfApp",
        "Name": "NameOfApp",
        "Version": "1.0.0.0"
    }
}

We will add a AppData class in the project:

public class AppData
{
    public string Id { get; init; }
    public string Name { get; init; }
    public string Version { get; init; }

    public override string ToString()
    {
        return $"Id: {Id}, Name: {Name}, Version: {Version}";
    }
}

To read the settings into the class, we could use JsonDeserializer from System.Text.Json:

using System.Text.Json;

var settingsText = File.ReadAllText("appsettings.json");
var settings = JsonSerializer.Deserialize<Settings>(settingsText);
Console.WriteLine(settings);

You need to define a class Settings to read the data:

public class Settings
{
    public AppData AppData { get; set; }
}

That’s fine, but let’s say you have different settings for development and production, you should have a copy of the appsettings.json with the modified values and some code like this one:

using System.Diagnostics;
using System.Text.Json;

string settingsText; 
if (Debugger.IsAttached)
{
    settingsText = File.ReadAllText("appsettings.development.json");
}
else
{
    settingsText = File.ReadAllText("appsettings.json");
}
var settings = JsonSerializer.Deserialize<Settings>(settingsText);
Console.WriteLine(settings);

Things start to become complicated. If there was a way to simplify this… In fact, yes there is. .NET provides us with the ConfigurationBuilder class. With it, you can read and merge several files to get the configuration. The following code will merge the appsettings.json and appsettings.development.json into a single class. In production, all you have to do is to remove the appsettings.development.json from the package and only the production file will be used.

To use the ConfigurationBuilder class you must add the NuGet packages Microsoft.Extensions.Configuration and Microsoft.Extensions.Configuration.Binder with

dotnet add package Microsoft.Extensions.Configuration
dotnet add package Microsoft.Extensions.Configuration.Binder

You will also have to add the Microsoft.Extensions.Configuration.Json package to use the AddJsonFile extension method.

One other thing that you will have to do is to tell msbuild to copy the settings file to the output directory. This is done by changing the csproj file, adding this clause

<ItemGroup>
  <Content Include="appsettings*.json">
    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
  </Content>  
</ItemGroup>

Once you do that, this code will read the config files, merge them and print the settings:

IConfigurationRoot config = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", false)
    .AddJsonFile("appsettings.development.json", true)
    .Build();
var appdata = config.GetSection(nameof(AppData)).Get<AppData>();
Console.WriteLine(appdata);

The second parameter in the AddJsonFile method tells that the appsettings.development.json file is optional and, if it’s not there, it wont be read. One other advantage is that I don’t need to duplicate all settings. You just need to add the overridden settings in the development file and the other ones will still be available.

Now, one more problem: let’s say we are using an API that requires a client Id and a client Secret. These values are very sensitive and they cannot be distributed. If you are using a public code repository, like GitHub, you cannot add something like this to appsettings.json and push your changes:

{
    "AppData": {
        "Id": "IdOfApp",
        "Name": "NameOfApp",
        "Version": "1.0.0.0"
    },
    "ApiData": {
        "ClientId": "ClientIdOfApp",
        "ClientSecret": "ClientSecretOfApp"
    }
}

That would be a real disaster, because your API codes would be open and you would end up with a massive bill at the end of the month. You could add these keys to appsettings.development.json and add it to the ignored files, so it wouldn’t be uploaded, but there is no guarantee that this won’t happen. Somebody could upload the file and things would be messy again.

The solution, in this case, would be to use the Secret Manager Tool. This tool allows you to store secrets in development mode, in a way that they cannot be shared to other users. This tool doesn’t encrypt any data and must only be used for development purposes. If you want to store the secrets in a safe encrypted way, you should use something like the Azure Key Vault.

To use it, you should initialize the storage with

dotnet user-secrets init

This will initialize the storage and generate a Guid for it and add it to the csproj file:

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <UserSecretsId>fc572277-3ded-4467-9c46-534a075f905b</UserSecretsId>
  </PropertyGroup>

Then, you need to add the package Microsoft.Extensions.Configuration.UserSecrets:

dotnet add package Microsoft.Extensions.Configuration.UserSecrets

We can now start utilizing the user secrets, by adding the new configuration type:

IConfigurationRoot config = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", false)
    .AddJsonFile("appsettings.development.json", true)
    .AddUserSecrets<Settings>()
    .Build();

Then, we can add the secret data:

dotnet user-secrets set "ApiData:ClientId" "ClientIdOfApp"
dotnet user-secrets set "ApiData:ClientSecret" "ClientSecretOfApp"

As you can see, the data is flattened in order to be added to the user secrets. You can take a look at it by opening an Explorer window and going to %APPDATA%\Microsoft\UserSecrets\{guid}\secrets.json:

{
  "ApiData:ClientId": "ClientIdOfApp",
  "ApiData:ClientSecret": "ClientSecretOfApp"
}

As you can see, there isn’t any secret here, it’s just a way to store data with no possibility to share it in an open repository.

You can get the values stored with

dotnet user-secrets list

To remove some key from the store, you can use something like

dotnet user-secrets remove ClientId

And to clear all data, you can use

dotnet user-secrets clear

If you have some array data to store, you will have to flatten in the same way, using the index of the element as a part of the name. For example, if you have something like

public class Settings
{
    public AppData AppData { get; set; }
    public ApiData ApiData { get; set; }
    public string[] AllowedHosts { get; set; }
}

You can store the AllowedHosts data with

dotnet user-secrets set "AllowedHosts:0" "microsoft.com"
dotnet user-secrets set "AllowedHosts:1" "google.com"
dotnet user-secrets set "AllowedHosts:2" "amazon.com"

And you can read the settings with some code like this:

IConfigurationRoot config = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", false)
    .AddJsonFile("appsettings.development.json", true)
    .AddUserSecrets<Settings>()
    .Build();
var settings = config.Get<Settings>();
foreach (var item in settings.AllowedHosts)
{
    Console.WriteLine(item);
}
Console.WriteLine(settings.AppData);
Console.WriteLine(settings.ApiData);

As you can see, if you need something to keep your development data safe from uploading to a public repository, you can use the user secrets in the same way you would do by using a json file. This simplifies a lot the storage of config files and allows every developer to have their own settings.

The full source code for this project is at https://github.com/bsonnino/SecretStorage

Introduction

I am a long time user of Gmail, and I usually don’t delete any email, I just archive the emails after reading and processing them, to keep my inbox clean.

Last week, I got a notice from Gmail that I was reaching the 15GB free limit and, in order to continue receiving emails, I should I should either buy some extra storage or clean my mail archive.

I know that I store a lot of garbage there, so I decided to clean the archive: the first step was to delete some old newsletters, and some junk email, but this didn’t even scratch the size of my mailbox (maybe it removed about 200Mb of data).

Then I started to use Gmail’s size filters: if you enter “larger:10M” in the query box, Gmail will show only messages with 10Mb or more. This is a great improvement, but there are two gotchas here: the messages aren’t sorted by size and you don’t know the size of every message.

That way, you won’t be able to effectively clean your mailbox – it will be a very difficult task to search among your 1500 messages which ones are good candidates to delete. So I decided to bite the bullet and create a C# program to scan my mailbox, list the largest ones and delete some of them. I decided to create a Universal Windows Platform app, so I could use on both my desktop and Windows Phone with no changes in the app code.

Registering the app with Google

The first step to create the app is to register it with Google, so you can get an app id to use in your app. Go to https://console.developers.google.com/flows/enableapi?apiid=gmail and create a new project. Once you have registered, you must get the credentials, to use in the app.

Figure 1 – Adding credentials

This will create new credentials for your app, which you must download and add to your project. When you download the credentials, you get a file named client_id.json , which you will include in your project. The next step is to create the project.

Creating the project

You must go to Visual Studio and create a new UWP app (blank app).

Figure 2 – Creating a new UWP app

A dialog appears asking you the target version for the app, and you can click OK. Then, you must add the NuGet package Google.Apis.Gmail.v1. This can be done in two ways: in the Solution Explorer, right click in the “References” node and select “Manage NuGet Packages” and search for Gmail, adding the Gmail package.

The second way is to open the Package Manager Console Window and adding the command:

Install-Package Google.Apis.Gmail.v1

Once you have installed the package, you must add the json file with the credentials to your project. Right click the project and select Add/Existing item and add the client_id.json file. Go to the properties window and select Build Action to Content and Copy to Output Directory as Copy always.

Getting User authorization

The first thing you must do in the program is to get the user authorization to access the email. This is done using OAuth2, with this code:

public async Task<UserCredential> GetCredential()
{
    var scopes = new[] { GmailService.Scope.GmailModify };
    var uri = new Uri("ms-appx:///client_id.json");
    _credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
        uri, scopes, "user", CancellationToken.None);
    return _credential;
}

We call the AuthorizeAsync method of GoogleWebAuthorizationBroker, passing the uri for client_id.json , andthe scope we want (modify emails). You can call this method in the constructor of MainPage:

public MainPage()
{
    this.InitializeComponent();
    GetCredential();
}

When you run the program, it will open a web page to get the user’s authorization. This procedure doesn’t store any password in the application, thus making it safe for the users: they can give their credentials to the Google broker and the broker will send just an authorization token to access the email.

Figure 3 – Web page for authorization

Figure 4 – Authorization consent

If you take a look at Figure 4, you will see that we are not asking for permission to delete the mail. We won’t need this permission, because we are going to just move the messages to trash. Then, the user will be able to review the messages and delete them permanently.

Getting emails

Once you have the authorization, you can get the emails from the server. In MainPage.xaml, add the UI for the application:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <Button Content ="Get Messages" Click="GetMessagesClick" 
            HorizontalAlignment="Right" Margin="5" Width="120"/>
    <ListView Grid.Row="1" x:Name="MessagesList" />
    <TextBlock Grid.Row="2" x:Name="CountText" Margin="5"/>
</Grid>

We will have a button to get the messages and add them to the listview. At the bottom, a textblock will display the message count. The code for retrieving the messages is:

private async void GetMessagesClick(object sender, RoutedEventArgs e)
{
    var service = new GmailService(new BaseClientService.Initializer()
    {
        HttpClientInitializer = _credential,
        ApplicationName = AppName,
    });
    UsersResource.MessagesResource.ListRequest request =
    service.Users.Messages.List("me");
    request.Q = "larger:5M";
    request.MaxResults = 1000;
    messages = request.Execute().Messages;
    MessagesList.ItemsSource = messages;
    CountText.Text = $"{messages.Count} messages";
}

We create a request for getting the messages larger than 5Mb and returning a maximum of 1000 results. If you have more than 1000 emails larger than 5Mb, there’s no guarantee you will get the largest emails, but you can change the query settings to satisfy your needs. Then, we query the server and fill the listview. If you run the app and click the button, you will see something like in Figure 5:

Figure 5 – Mail results displayed in the app window

We see only the item type because we didn’t set up an item template. That can be done in MainPage.xaml:

<ListView Grid.Row="1" x:Name="MessagesList" >
    <ListView.ItemTemplate>
        <DataTemplate>
            <StackPanel Margin="5">
                <TextBlock Text="{Binding Id}"/>
                <TextBlock Text="{Binding Snippet}" FontWeight="Bold"/>
                <TextBlock Text="{Binding SizeEstimate}"/>
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Running the app, you will see that the list only shows the Ids for the messages. This first call doesn’t return the full message. To get the message contents, we must do a second call, to retrieve the message contents:

private async void GetMessagesClick(object sender, RoutedEventArgs e)
{
    var service = new GmailService(new BaseClientService.Initializer()
    {
        HttpClientInitializer = _credential,
        ApplicationName = AppName,
    });
    
        UsersResource.MessagesResource.ListRequest request =
        service.Users.Messages.List("me");
        request.Q = "larger:5M";
        request.MaxResults = 1000;
        messages = request.Execute().Messages;
        var sizeEstimate = 0L;
        for (int index = 0; index < messages.Count; index++)
        {
            var message = messages[index];
            var getRequest = service.Users.Messages.Get("me", message.Id);
            getRequest.Format =
                UsersResource.MessagesResource.GetRequest.FormatEnum.Metadata;
            getRequest.MetadataHeaders = new Repeatable<string>(
                new[] { "Subject", "Date", "From" });
            messages[index] = getRequest.Execute();
            sizeEstimate += messages[index].SizeEstimate ?? 0;
        }
    });
    MessagesList.ItemsSource = messages.OrderByDescending(m => m.SizeEstimate));
    CountText.Text = $"{messages.Count} messages. Estimated size: {sizeEstimate:n0}";
}

When we are getting the messages, we limit the data recovered. The default behavior for the Get request is to retrieve the full message, but this would be an overkill. We only get the Subject, Date and From headers for the message. If you run the app, you will get the snippet and the size, but you will see that the app hangs while retrieving the messages. This is not a good thing to do. We must get the messages in the background:

private async void GetMessagesClick(object sender, RoutedEventArgs e)
{
    var service = new GmailService(new BaseClientService.Initializer()
    {
        HttpClientInitializer = _credential,
        ApplicationName = AppName,
    });
    var sizeEstimate = 0L;
    IList<Message> messages = null;
    
    await Task.Run(async () =>
    {
        UsersResource.MessagesResource.ListRequest request =
        service.Users.Messages.List("me");
        request.Q = "larger:5M";
        request.MaxResults = 1000;
        messages = request.Execute().Messages;
        
        for (int index = 0; index < messages.Count; index++)
        {
            var message = messages[index];
            var getRequest = service.Users.Messages.Get("me", message.Id);
            getRequest.Format =
                UsersResource.MessagesResource.GetRequest.FormatEnum.Metadata;
            getRequest.MetadataHeaders = new Repeatable<string>(
                new[] { "Subject", "Date", "From" });
            messages[index] = getRequest.Execute();
            sizeEstimate += messages[index].SizeEstimate ?? 0;
        }
    });
    MessagesList.ItemsSource = messages.OrderByDescending(m => m.SizeEstimate));
    CountText.Text = $"{messages.Count} messages. Estimated size: {sizeEstimate:n0}";
}

Now the code doesn’t block the UI, but there’s no indication of what’s happening. Let’s add a progress bar to the UI:

<TextBlock Grid.Row="2" x:Name="CountText" Margin="5"/>
<Border x:Name="BusyBorder" Grid.Row="0" Grid.RowSpan="3" 
        Background="#40000000" Visibility="Collapsed">
    <StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
        <TextBlock Text="downloading messages" x:Name="OperationText"/>
        <ProgressBar x:Name="ProgressBar" Margin="0,5"/>
        <TextBlock x:Name="DownloadText"  HorizontalAlignment="Center"/>
    </StackPanel>
</Border>

To update the progress bar while downloading, we use this code:

private async void GetMessagesClick(object sender, RoutedEventArgs e)
{
    var service = new GmailService(new BaseClientService.Initializer()
    {
        HttpClientInitializer = _credential,
        ApplicationName = AppName,
    });
    var sizeEstimate = 0L;
    IList<Message> messages = null;
    
    BusyBorder.Visibility = Visibility.Visible;
    await Task.Run(async () =>
    {
        UsersResource.MessagesResource.ListRequest request =
        service.Users.Messages.List("me");
        request.Q = "larger:5M";
        request.MaxResults = 1000;
        messages = request.Execute().Messages;
        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            ProgressBar.Maximum = messages.Count);

        for (int index = 0; index < messages.Count; index++)
        {
            var message = messages[index];
            var getRequest = service.Users.Messages.Get("me", message.Id);
            getRequest.Format =
                UsersResource.MessagesResource.GetRequest.FormatEnum.Metadata;
            getRequest.MetadataHeaders = new Repeatable<string>(
                new[] { "Subject", "Date", "From" });
            messages[index] = getRequest.Execute();
            sizeEstimate += messages[index].SizeEstimate ?? 0;
            
            var index1 = index+1;
            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                ProgressBar.Value = index1;
                DownloadText.Text = $"{index1} of {messages.Count}";
            });
        }
    });
    BusyBorder.Visibility = Visibility.Collapsed;
    MessagesList.ItemsSource = messages.OrderByDescending(m => m.SizeEstimate));
    CountText.Text = $"{messages.Count} messages. Estimated size: {sizeEstimate:n0}";
}

We set the visibility of the Busy border to visible before downloading the messages. As we download the messages, we update the progress bar. We are running the code in a background thread, so we can’t update the progress bar and the text directly, we must use the Dispatcher to update the controls in the main thread. Now, when we run the code, the busy border is shown and the progress bar is updated with the download count. At the end, we get the messages, with the snippets and size.

Figure 6 – Message results

You can see some problems in this display:

  • It’s far from good, and should be improved
  • It doesn’t show the subject, date and who sent the message
  • The snippet format is encoded and should be decoded
  • The size could be formatted

We can fix these issues by creating a new class:

public class EmailMessage
{
    public string Id { get; set; }
    public bool IsSelected { get; set; }
    public string Snippet { get; set; }
    public string SizeEstimate { get; set; }
    public string From { get; set; }
    public string Date { get; set; }
    public string Subject { get; set; }
}

And use it, instead of the Message class:

private async void GetMessagesClick(object sender, RoutedEventArgs e)
{
    var service = new GmailService(new BaseClientService.Initializer()
    {
        HttpClientInitializer = _credential,
        ApplicationName = AppName,
    });
    var sizeEstimate = 0L;
    IList<Message> messages = null;
    var emailMessages = new List<EmailMessage>();
    OperationText.Text = "downloading messages";
    BusyBorder.Visibility = Visibility.Visible;
    await Task.Run(async () =>
    {
        UsersResource.MessagesResource.ListRequest request =
        service.Users.Messages.List("me");
        request.Q = "larger:5M";
        request.MaxResults = 1000;
        messages = request.Execute().Messages;
        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            ProgressBar.Maximum = messages.Count);

        for (int index = 0; index < messages.Count; index++)
        {
            var message = messages[index];
            var getRequest = service.Users.Messages.Get("me", message.Id);
            getRequest.Format =
                UsersResource.MessagesResource.GetRequest.FormatEnum.Metadata;
            getRequest.MetadataHeaders = new Repeatable<string>(
                new[] { "Subject", "Date", "From" });
            messages[index] = getRequest.Execute();
            sizeEstimate += messages[index].SizeEstimate ?? 0;
            emailMessages.Add(new EmailMessage()
            {
                Id = messages[index].Id,
                Snippet = WebUtility.HtmlDecode(messages[index].Snippet),
                SizeEstimate = $"{messages[index].SizeEstimate:n0}",
                From = messages[index].Payload.Headers.FirstOrDefault(h => 
                    h.Name == "From").Value,
                Subject = messages[index].Payload.Headers.FirstOrDefault(h => 
                    h.Name == "Subject").Value,
                Date = messages[index].Payload.Headers.FirstOrDefault(h => 
                    h.Name == "Date").Value,
            });
            var index1 = index+1;
            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                ProgressBar.Value = index1;
                DownloadText.Text = $"{index1} of {messages.Count}";
            });
        }
    });
    BusyBorder.Visibility = Visibility.Collapsed;
    MessagesList.ItemsSource = new ObservableCollection<EmailMessage>(
        emailMessages.OrderByDescending(m => m.SizeEstimate));
    CountText.Text = $"{messages.Count} messages. Estimated size: {sizeEstimate:n0}";
}

With this new code, we can change the item template, to show the new data:

<ListView.ItemTemplate>
    <DataTemplate>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="40"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <CheckBox HorizontalAlignment="Left" VerticalAlignment="Center" 
                      IsChecked="{Binding IsSelected, Mode=TwoWay}" Margin="5"/>
            <StackPanel Grid.Column="1" Margin="5">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="From:" Margin="0,0,5,0"/>
                    <TextBlock Text="{Binding From}"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Date:" Margin="0,0,5,0"/>
                    <TextBlock Text="{Binding Date}"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="Size: " Margin="0,0,5,0"/>
                    <TextBlock Text="{Binding SizeEstimate}"/>
                </StackPanel>
                <TextBlock Text="{Binding Subject}"/>
                <TextBlock Text="{Binding Snippet}" FontWeight="Bold"/>
            </StackPanel>
            <Rectangle Grid.Column="0" Grid.ColumnSpan="2" 
                       HorizontalAlignment="Stretch" VerticalAlignment="Bottom" 
                       Height="1" Fill="Black"/>
        </Grid>
    </DataTemplate>
</ListView.ItemTemplate>

With this code, we get a result like this:

Figure 7 – Message results with more data

We could still improve the performance of the app by making multiple requests for messages at the same time, but I leave this for you.

Deleting emails from the server

Now, the only task that remains is to delete the emails from the server. For that, we must add another button:

<Button Content ="Get Messages" Click="GetMessagesClick" 
        HorizontalAlignment="Right" Margin="5" Width="120"/>
<Button Grid.Row="0" Content ="Delete Messages" Click="DeleteMessagesClick" 
        HorizontalAlignment="Right" Margin="5,5,140,5" Width="120"/>

The code for deleting messages is this:

private async void DeleteMessagesClick(object sender, RoutedEventArgs e)
{
    var messages = (ObservableCollection<EmailMessage>) MessagesList.ItemsSource;
    var messagesToDelete = messages.Where(m => m.IsSelected).ToList();
    if (!messagesToDelete.Any())
    {
        await (new MessageDialog("There are no selected messages to delete")).ShowAsync();
        return;
    }
    var service = new GmailService(new BaseClientService.Initializer()
    {
        HttpClientInitializer = _credential,
        ApplicationName = AppName,
    });
    OperationText.Text = "deleting messages";
    ProgressBar.Maximum = messagesToDelete.Count;
    DownloadText.Text = "";
    BusyBorder.Visibility = Visibility.Visible;
    var sizeEstimate = messages.Sum(m => Convert.ToInt64(m.SizeEstimate));
    await Task.Run(async () =>
    {
        for (int index = 0; index < messagesToDelete.Count; index++)
        {
            var message = messagesToDelete[index];
            var response = service.Users.Messages.Trash("me", message.Id);
            response.Execute();
            var index1 = index+1;
            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                ProgressBar.Value = index1;
                DownloadText.Text = $"{index1} of {messagesToDelete.Count}";
                messages.Remove(message);
                sizeEstimate -= Convert.ToInt64(message.SizeEstimate);
                CountText.Text = $"{messages.Count} messages. Estimated size: {sizeEstimate:n0}";
            });
        }
    });
    BusyBorder.Visibility = Visibility.Collapsed;
}

If you run the app, you can select the messages you want and delete them. They will be moved to the trash folder, so you can double check the messages before deleting them.

Conclusion

This article has shown how to access the Gmail messages using the Gmail API and delete the largest messages, all in your Windows 10 app. Oh, and did I mention that the same app works also in Windows Phone, or in other Windows 10 devices?

All the source code for this article is available on GitHub, at http://github.com/bsonnino/LargeEmailsGmail

This article was first published at https://learn.microsoft.com/en-us/archive/blogs/mvpawardprogram/accessing-and-deleting-large-e-mails-in-gmail-with-c