When you buy this book you support this site! - Thank You for your support!

Oct 4

Written by: Michael Washington
10/4/2019 1:24 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

When they use the application for the first time, GMail will require the user to approve access.

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.

Open it in Visual Studio 2019 (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 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 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).

 
@using MimeKit
@using MessageReader
@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>
@code {
    [Parameter] public Message paramMessage { get; set; }
    MimeMessage MimeKitMessage;
    string strError = "";
    string htmlEmail = "";
    protected override void OnInitialized()
    {
        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”).

 

@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>
@code {
    [Parameter] public 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.

 

@page "/email"
@using System.Security.Claims
@inject IConfiguration _configuration
@strError
<br />
<br />
<!-- AuthorizeView allows us to only show sections of the page -->
<!-- based on the security on the current user -->
<AuthorizeView>
    <!-- Show this section if the user is logged in -->
    <Authorized>
        @if (Gmaillabels != null)
        {
            <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>
        }
    </Authorized>
    <!-- Show this section if the user is not logged in -->
    <NotAuthorized>
        <p>You must be logged in to use this page</p>
    </NotAuthorized>
</AuthorizeView>
@code {
    [CascadingParameter]
    private Task<AuthenticationState> authenticationStateTask { get; set; }
    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 OnInitializedAsync()
    {
        try
        {
            // Get the current user
            User = (await authenticationStateTask).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

 

Download

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

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

Tags: Blazor
Categories:

2 comment(s) so far...


Gravatar

Re: Google Email Viewer in Server Side Blazor

Great blog post, you're pushing all the boundaries, thank you for sharing!

By Karl on   10/5/2019 4:43 AM
Gravatar

Re: Google Email Viewer in Server Side Blazor

@Karl - Thanks!

By Michael Washington on   10/5/2019 4:43 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