Apr 6

Written by: Michael Washington
4/6/2019 12:10 PM  RssIcon

You can create a Blazor server-side application that will allow any user with a GMail account to view their emails.

This solution does not require that they set their GMail account to less secure access.

image

After the application is set up, any user with a GMail account can log into the application by clicking the Login button.

image

They enter their normal GMail username and password.

image

Their emails will display HTML content as well as embedded images.

 

image

If they log into the normal GMail website and tag an email…

 

image

When viewing that email in the Blazor GMail application, they will see that the email will show up when clicking on the button that has the same name as the tag.

 

Set Up The Application

image

We start with the code from the article: Google Authentication in Server Side Blazor (Razor Components).

Open it in Visual Studio 2019 Preview (or later).

image

Add the following NuGet packages:

  • Google.Apis.Gmail.v1
  • MailKit

 

Enable GMail API

You need to add the Google GMail API.

Go to: https://console.developers.google.com/apis/dashboard

Sign in with your Google account and select the project used to generate keys for the Google Authentication in Server Side Blazor (Razor Components) project.

image

Select Enable APIs and Services.

image

Search for and select GMail.

image

Click the Enable button to enable the API.

image

In Visual Studio, open the Components/_ViewImports.cshtml file and add the following code:

 

@using Microsoft.Extensions.Configuration
@using System.Threading
@using System.Text
@using System.IO
@using Google.Apis.Auth.OAuth2
@using Google.Apis.Gmail.v1
@using Google.Apis.Gmail.v1.Data
@using Google.Apis.Services
@using Google.Apis.Util.Store

 

Building The Application

image

All of the remaining code is contained in the Components/Pages/GMail folder.

The diagram above shows what part of the application each file is responsible for generating.

 

HTMLPreviewVisitor.cs 

A class that is passed a reference to a MimeMessage and generates HTML suitable to be rendered by a browser control.

The code is from: https://github.com/jstedfast/MimeKit/blob/master/samples/MessageReader/MessageReader/HtmlPreviewVisitor.cs

 

EmailMessageControl.razor

This displays a single message. It is passed a Google Message object that is then converted to a MimeKitMessage object because that object allows for easier C# manipulation and allows us to call the HTMLPreviewVisitor class that displays embedded images in the email and will also allow access to any attachments in the email (that code is commented out).

 

@page "/EmailMessageControl"
@using Microsoft.AspNetCore.Http
@using MimeKit
@using MessageReader
@inject IHttpContextAccessor _httpContextAccessor
@inject IConfiguration _configuration
@strError
<div style="padding:2px; vertical-align:top">
    <div><i>@MimeKitMessage.Date.ToString()</i></div>
    <div><b>From:</b> @MimeKitMessage.From.ToString()</div>
    <div><b>To:</b> @MimeKitMessage.To.ToString()</div>
    <div><b>Subject:</b> @MimeKitMessage.Subject</div>
    <br />
    <div>@((MarkupString)@htmlEmail)</div>
</div>
@functions {
    [Parameter] Message paramMessage { get; set; }
    MimeMessage MimeKitMessage;
    string strError = "";
    string htmlEmail = "";
    protected override void OnInit()
    {
        try
        {
            if (paramMessage != null)
            {
                string converted = paramMessage.Raw.Replace('-', '+');
                converted = converted.Replace('_', '/');
                byte[] decodedByte = Convert.FromBase64String(converted);
                using (Stream stream = new MemoryStream(decodedByte))
                {
                    // Convert to MimeKit from GMail
                    // Load a MimeMessage from a stream
                    MimeKitMessage = MimeMessage.Load(stream);
                    // Convert any embedded images
                    var visitor = new HtmlPreviewVisitor();
                    MimeKitMessage.Accept(visitor);
                    htmlEmail = visitor.HtmlBody;
                    //If the email has attachments we can get them here
                    //var attachments = visitor.Attachments;
                }
            }
        }
        catch (Exception ex)
        {
            strError = ex.Message;
        }
    }
}

 

EmailFolderControl.razor

This displays the emails. It is passed a reference to the Google API so that it can retrieve the emails. The GetFolderContents method is marked public so that it can be invoked by the Email.razor control that it is contained in. the Email.razor control will pass the GetFolderContents method the selected tag (such as “Inbox”).

 

@using Microsoft.AspNetCore.Http
@page "/EmailFolderControl"
@inject IHttpContextAccessor _httpContextAccessor
@inject IConfiguration _configuration
@strError
<table>
    <tbody>
        <tr>
            <td style="width:auto; text-align:left; vertical-align:top">
                @foreach (var message in ColMessages)
                {
                    <EmailMessageControl paramMessage="@message">
                    </EmailMessageControl>
                    <br />
                    <hr size="20">
                }
            </td>
        </tr>
    </tbody>
</table>
@functions {
    [Parameter] GmailService service { get; set; }
    List<Message> ColMessages = new List<Message>();
    string strError = "";
    public void GetFolderContents(string folder)
    {
        try
        {
            strError = "";
            ColMessages = new List<Message>();
            // Get all the EMAIL folders for the current user ("me")
            var request = service.Users.Messages.List("me");
            request.LabelIds = folder;
            // Used for paging
            //request.MaxResults = 10;
            //request.PageToken = 1;            
            // Get messages
            var Messages = request.Execute();
            // do we have any messages?
            if (Messages.ResultSizeEstimate > 0)
            {
                foreach (var email in Messages.Messages)
                {
                    // Get the selected Email
                    var emailInfoReq = service.Users.Messages.Get("me", email.Id);
                    // Must request the *raw* version so that we get a .EML
                    // version that we can use to be converted to MimeKit
                    emailInfoReq.Format = 
                        UsersResource.MessagesResource.GetRequest.FormatEnum.Raw;
                    // Load a MimeMessage from a stream
                    Message GMailMessage = emailInfoReq.Execute();
                    // do not add message if already in the collection
                    if (!ColMessages.Any(x => x.Id == GMailMessage.Id))
                    {
                        ColMessages.Add(GMailMessage);
                    }
                }
            }
        }
        catch (Exception ex)
        {
            strError = ex.Message;
        }
    }
}

 

 

Email.razor

This is the main page for the Email application.

The primary purpose of this control is to connect to the GMail API.

This also displays a list of available tags that the GMail account has configured and also contains the EmailFolderControl. When a user selects a tag, it calls the GetFolderContents method in the EmailFolderControl passing the selected tag. The EmailFolderControl then displays the emails that have that tag.

 

@using System.Security.Claims
@using Microsoft.AspNetCore.Http
@page "/email"
@inject IHttpContextAccessor _httpContextAccessor
@inject IConfiguration _configuration
@strError
<br /><br />
@if (User.Identity.IsAuthenticated)
{
    @if (Gmaillabels == null)
    {
        <p><em>Loading...</em></p>
    }
    else
    {
        <table>
            <tbody>
                <tr>
                    <td style="width:200px; text-align:left; vertical-align:top">
                        @foreach (var EmailFolder in Gmaillabels)
                        {
                            <div style="padding:2px; vertical-align:top">
                                <button class="btn btn-primary btn-sm btn-block"
                                        bind="@EmailFolder"
                                        onclick="@(() => ShowFolder(EmailFolder))">
                                    @EmailFolder.Name
                                </button>
                            </div>
                        }
                    </td>
                    <td><EmailfolderControl ref="_emailFolder" service="@service">
                        </EmailfolderControl></td>
                </tr>
            </tbody>
        </table>
    }
}
else
{
    <p>You must be logged in to use this page</p>
}
@functions {
    EmailFolderControl _emailFolder;
    private ClaimsPrincipal User;
    GmailService service;
    string folder = "INBOX";
    string strError = "";
    UserCredential credential;
    public List<Label> Gmaillabels = new List<Label>();
    static string[] Scopes = { GmailService.Scope.GmailReadonly };
    static string ApplicationName = "Blazor GMail";
    protected override async Task OnInitAsync()
    {
        try
        {
            // Set the user to determine if they are logged in
            User = _httpContextAccessor.HttpContext.User;
            if (User.Identity.IsAuthenticated)
            {
                // Get the credentials for the current user that is stored in a
                // directory that serves as a common repository for documents.
                string credPath =
                    System.Environment
                    .GetFolderPath(System.Environment.SpecialFolder.Personal);
                credPath = Path.Combine(credPath, ".credentials/",
                    System.Reflection.Assembly.GetExecutingAssembly().GetName().Name);
                // Set the settings from the appsettings.json file
                ClientSecrets objClientSecrets = new ClientSecrets();
                objClientSecrets.ClientId = _configuration["Google:ClientId"];
                objClientSecrets.ClientSecret = _configuration["Google:ClientSecret"];
                // Requesting Authentication or loading previously stored
                // authentication for userName
                credential = 
                    await GoogleWebAuthorizationBroker
                    .AuthorizeAsync(objClientSecrets,
                    Scopes,
                    User.Identity.Name,
                    CancellationToken.None,
                    new FileDataStore(credPath, true));
                // Create Gmail API service.
                service = new GmailService(new BaseClientService.Initializer()
                {
                    HttpClientInitializer = credential,
                    ApplicationName = ApplicationName,
                    ApiKey = _configuration["Google:ClientSecret"]
                });
                // Get all the EMAIL folders for the current user ("me")
                UsersResource.LabelsResource.ListRequest request = 
                    service.Users.Labels.List("me");
                // List Email Folders (as Google Labels)
                List<Label> ColLabels =
                    request.Execute().Labels
                    .ToList()
                    .OrderBy(x => x.Name)
                    .ToList();
                // Add Inbox as first Label
                Gmaillabels.Add(ColLabels.Where(x => x.Name == "INBOX")
                    .FirstOrDefault());
                // Add all labels except INBOX (that has already been added)
                // that are of type "user"
                foreach (var item in ColLabels
                    .Where(x => x.Name != "INBOX" && x.Type == "user"))
                {
                    Gmaillabels.Add(item);
                }
            }
        }
        catch (Exception ex)
        {
            strError = ex.Message;
        }
    }
    // Call this method to show the contents of 
    // the selected folder (Tag)
    void ShowFolder(Label paramEmailFolder)
    {
        Label EmailFolder = paramEmailFolder;
        folder = EmailFolder.Id;
        _emailFolder.GetFolderContents(folder);
    }
}
 

Links

Blazor.net

Google Developers Dashboard

Using OAuth 2.0 to Access Google APIs

GMail API .Net Quickstart

External Login Providers in ASP.NET Core

Implementing Google OAuth with Blazor (0.4) and ASPNET Core 2.1.1

Google external login setup in ASP.NET Core

 

Download

The project is available at http://lightswitchhelpwebsite.com/Downloads.aspx

You must have Visual Studio 2019 (or higher) installed to run the code.

2 comment(s) so far...


Gravatar

Re: Google Email Viewer in Server Side Blazor

I just wanted the ls back

By Rodrigo on   4/7/2019 3:45 AM
Gravatar

Re: Google Email Viewer in Server Side Blazor

@Rodrigo - Yeah "ls" (LightSwitch) had a good run, but, all technology eventually 'fades away'... 🥺

By Michael Washington on   4/7/2019 3:47 AM

Your name:
Gravatar Preview
Your email:
(Optional) Email used only to show Gravatar.
Your website:
Title:
Comment:
Security Code
CAPTCHA image
Enter the code shown above in the box below
Add Comment   Cancel 
Microsoft Visual Studio is a registered trademark of Microsoft Corporation / LightSwitch is a registered trademark of Microsoft Corporation