Visual Studio Extensibility - Creating Extension To Get Mail Notifications In IDE

Through this article, learn how to create extensions to get your email notifications in Visual Studio IDE.

Introduction

Consider a situation where you are coding for hours in Visual Studio and don’t have time to check your emails in a web browser. Due to this, you may miss some of your important notifications. Of course, there are ways to get notifications of your emails but imagine how good it would be if you could get your email notifications straight in your Visual Studio. That’s what we are going to do today.

In this article, we will make a Visual Studio extension (Visual Studio VSIX Package) that would notify us for our emails in Visual Studio Status bar.

Visual Studio Extensibility

Visual Studio Extensibility allows developers to develop their own custom pluggable extensions to Visual Studio IDE to customize Visual Studio and add functionality as per their need. Simply stated, you can create your own functionality that you want Visual Studio to have and add to it. So, we will create our own functionality to fetch email notifications and show it in the status bar of visual.

If you want a kick start before proceeding to this article, you can first refer this article by Akhil Mittal which will help you create your first VSIX package.

What we are going to create

Let’s just take a glimpse of what we are going to create in the end : Custom Tool Window to let users login to  their email account.

Email Notification in status bar of Visual Studio -

Prerequisites

Before we start developing, make sure you have the following.

  • Visual Studio 2013 or latest. I am using Visual Studio 2013 Community Version.
  • Visual Studio SDK

    • VS 2013 can be downloaded from here.
    • The Visual Studio 2015 SDK is no longer offered as a separate download. Instead, Visual Studio Extensibility Tools (SDK and templates) are included as an optional feature in Visual Studio setup.

Getting your hand dirty

Well, we have talked a lot. Now, it’s time for some action. Let’s start developing our extension for Visual Studio. I will guide you through all the steps needed to be taken to develop a VS Extension. Now, fasten your seat belt and get ready for fun ride.

Step 1 - Creating VS Package

Create a new project of type Visual Studio Package from the Extensibility category. Choose a name and location for the project.

After clicking on ‘OK’, a wizard will appear that will guide you through some initial steps.

Click on ‘Next’ to proceed.

Next, the Wizard will ask you to choose your programming language in which you want to develop.

Choose Visual C# because we are going to write our code in C# language and then click on ‘Next’.

The next screen will ask you the details of your package.

Give company name, VSPackage name and details according to your choice and then click on ‘Next’.

The next screen will ask you for the options you want to include in your VS package. Check on Tool Window because we will require one to get user email credentials.

On next screen, type the details of the ‘Tool Window’ that you included in the previous step.

Window name is the name that will appear in the Visual Studio windows list and commandId is the id that will be used in code to uniquely identify it.

Next screen will ask you whether you want to include tests for your project. In this article, I have not selected this option as I am not writing any test. This is the final step of wizard. Click on Finish.

A project hierarchy will be created for you that will include required files for your VS Package.

Step 2 - Creating Tool Window

By default, a dummy Tool Window is already created in the project because we have selected one in the wizard. Let’s modify that tool window according to our need.

Open MyControl.xaml and replace the code with the following.

  1. <UserControl x -Class="Microsoft.MailNotifier.MyControl"  
  2.              xmlns="http -//schemas.microsoft.com/winfx/2006/xaml/presentation"  
  3.              xmlns -x="http -//schemas.microsoft.com/winfx/2006/xaml"  
  4.              xmlns -mc="http -//schemas.openxmlformats.org/markup-compatibility/2006"  
  5.              xmlns -d="http -//schemas.microsoft.com/expression/blend/2008"  
  6.              Background="{DynamicResource VsBrush.Window}"  
  7.              Foreground="{DynamicResource VsBrush.WindowText}"  
  8.              mc -Ignorable="d"  
  9.              d -DesignHeight="300" d -DesignWidth="300"  
  10.              Name="MyToolWindow">  
  11.     <Grid>  
  12.         <StackPanel Orientation="Vertical">  
  13.             <TextBlock Margin="10" HorizontalAlignment="Center">Notifier</TextBlock>  
  14.             <Grid >  
  15.                 <TextBlock Margin="25,0,0,0">E-Mail</TextBlock>  
  16.                 <TextBox Width="200" Margin="60,0,0,0" Text="{Binding Email,UpdateSourceTrigger=PropertyChanged}"></TextBox>  
  17.             </Grid>  
  18.             <Grid >  
  19.                 <TextBlock Margin="25,0,0,0">Password</TextBlock>  
  20.                 <PasswordBox Width="200" Margin="60,0,0,0" Name="MyPasswordBox"></PasswordBox>  
  21.             </Grid>  
  22.   
  23.             <Button Content="Sign In" Command="{Binding SignInCommand}"   
  24.                     Margin="5"  
  25.                     Width="100" Height="30" Name="button1"  
  26.                     CommandParameter="{Binding ElementName=MyPasswordBox}"/>  
  27.         </StackPanel>  
  28.     </Grid>  
  29. </UserControl>  
 

This is basically XAML code to create some text blocks, text box, password control and button.

UI is created for our tool window.

Let’s see what other files we have.

  • MyToolWindow.cs - It contains the MyToolWindow class that inherits from ToolWindowPane which has its unique guid and content is set to our XAML control class. We don’t need to modify it.
  • PkgCmdID.cs - it contains uint code for command to open tool window. We also do not deed to modify it.
  • source.extension.vsixmanifest - it contains details of our package, we have already modified it during wizard step.
  • MailNotifierPackage.cs – it is the main package that owns our tool window it has methods to show and hide tool window. It add option in menu of Visual Studio to open Tool window. In our case keep it un-touched.
  • MailNotifier.vsct – this is the file where you define menu items, their icons and title, shortcut keys etc. but Visual Studio has already created a menu to open tool window so we do not need to touch.

See most of the work has already been done by Visual Studio.

Step 3 - Creating View Model for Tool Window

Add a new class file to project and named it NotifierToolWindowViewModel. This will be our view model for tool window.

We need a property to bind to email text box and we need a command to be on click of sign in button.

So add property and delegate command in view model.

  1. public class NotifierToolWindowViewModel  
  2.     {  
  3.         private string _email;  
  4.         private DelegateCommand signIn;  
  5.   
  6.         public NotifierToolWindowViewModel()  
  7.         {  
  8.             signIn = new DelegateCommand(SignIn);  
  9.         }  
  10.   
  11.         private void SignIn(object obj)  
  12.         {  
  13.               
  14.   
  15.              
  16.         }  
  17.         public ICommand SignInCommand  
  18.         {  
  19.             get { return signIn; }  
  20.         }  
  21.         public string Email  
  22.         {  
  23.             get { return _email; }  
  24.             set { _email = value; }  
  25.         }  
  26.   
  27.     }  
  28.     public class DelegateCommand  - ICommand  
  29.     {  
  30.         private Action<object> _executeMethod;  
  31.         public DelegateCommand(Action<object> executeMethod)  
  32.         {  
  33.             _executeMethod = executeMethod;  
  34.         }  
  35.         public bool CanExecute(object parameter)  
  36.         {  
  37.             return true;  
  38.         }  
  39.         public event EventHandler CanExecuteChanged;  
  40.         public void Execute(object parameter)  
  41.         {  
  42.             _executeMethod.Invoke(parameter);  
  43.         }  
  44.     }  
  45. }  
 

If your background is WPF then I am sure it is quite easy to understand.

It’s time to build the solution and see what we have achieved until now. Press F5 to run the project. When you run the project an experimental instance of Visual Studio will be open.

Go to View->Other Windows, there you will find our tool window named as ‘Notifier’.

Click on it to open the tool window.

Yeah! Our half of the work is done. Now we need some mechanism through which we can connect to our email and get notifications. There is no implementation in .NET to get emails but there is SMTP client to send emails (though it will not fulfill our needs). This can be achieved using IMAP (Internet Message Access Protocol) and IMAP IDLE which is responsible to notify users to immediately receive any mailbox changes. There are libraries available which can help us achieve what we want so I have S22.Imap library. It can be downloaded from Nuget gallery http -//www.nuget.org/packages/S22.Imap/ and we can include it in our project using package console manager with the command ‘Install-Package S22.Imap’.

Now we have S22.Imap DLL included in our project but before we proceed further we need to register this newly added DLL with a strong name because VS Packages requires all assemblies to be signed with a strong name. To register S22.Imap DLL with a strong name please follow the step mentioned in this link. I have already done that for you and then have included the DLL in the project, you could use the one attached with the article.

Step 4 - Implementing S22.Imap for email notifications

S22.Imap library is pretty simple to use, we just need to create ImapClient object and then need to pass our credentials and we are good to go.

Let’s see code to use ImapClient.

  1. private ImapClient client;  
  2.   
  3. private void InitializeClient(PasswordBox pwBox)  
  4.  {  
  5.             // Dispose of existing instance, if any.  
  6.             if (client != null)  
  7.                 client.Dispose();  
  8.             client = new ImapClient("imap.gmail.com", 993,"username","password", AuthMethod.Login, true);  
  9.             // Setup event handlers.  
  10.             client.NewMessage += client_NewMessage;  
  11.             client.IdleError += client_IdleError;  
  12.   }  
 

ImapClient constructor takes hostname, port, username, password and true (means SSL =true) to connect your email account. Then we can subscribe its two events to get notified when a new mail is received and some error occur during idle waiting for mail.

  1. private void client_IdleError(object sender, IdleErrorEventArgs e)  
  2. {  
  3.     // code to handle error  
  4.   
  5. }  
  6. private void client_NewMessage(object sender, IdleMessageEventArgs e)  
  7. {  
  8.     //code to handle new mail  
  9. }  
 

Now, we are good to get notified whenever a new mail is received, but there is a catch here. If we try to connect our Gmail account, it will throw some connection error because Gmail is very secure and doesn’t let less secure applications to make connections with your account for the purpose of your security. So, to make our application connect with Gmail, we need to change settings of our account to allow connections from less secure application.

Follow this link to set its security https -//www.google.com/settings/security/lesssecureapps

Everything is set now to receive notification.

Step 5 - Displaying our custom text in Visual Studio Status bar

To display our custom text in Visual Studio Status bar, we need to implement the IVsStatusbar service of Visual Studio which provides method to set text in status bar. Let’s see the code.

  1. private IVsStatusbar StatusBar  
  2. {  
  3.     get  
  4.     {  
  5.         if (bar == null)  
  6.         {  
  7.             bar = Package.GetGlobalService(typeof(SVsStatusbar)) as IVsStatusbar;  
  8.         }  
  9.   
  10.         return bar;  
  11.     }  
  12. }  
  13. /// <summary>  
  14. /// Displays the message.  
  15. /// </summary>  
  16. public void DisplayMessage(string msg)  
  17. {  
  18.     int frozen;  
  19.   
  20.     StatusBar.Clear();  
  21.     StatusBar.IsFrozen(out frozen);  
  22.     StatusBar.SetText(msg);  
  23.     StatusBar.FreezeOutput(0);  
  24.   
  25. }  
  26.   
  27. /// <summary>  
  28. /// Display message and show icon  
  29. /// </summary>  
  30. /// <param name="message"></param>  
  31. /// <param name="iconToShow"></param>  
  32. public void DisplayAndShowIcon(string message, object iconToShow)  
  33. {  
  34.     object icon = (short)iconToShow;  
  35.   
  36.     StatusBar.Animation(1, ref icon);  
  37.     StatusBar.SetText(message);  
  38.     Thread.Sleep(3000);  
  39.   
  40.     StatusBar.Animation(0, ref icon);  
  41.     StatusBar.FreezeOutput(0);  
  42.     StatusBar.Clear();  
  43. }  

SetText() is the method that will do the magic.

We have all the parts in hands, all we need now is to assemble these parts and complete our code.

Below is the complete code of the NotifierToolWindowViewModel class.

  1. using Microsoft.VisualStudio.Shell;  
  2. using Microsoft.VisualStudio.Shell.Interop;  
  3. using S22.Imap;  
  4. using System;  
  5. using System.Collections.Generic;  
  6. using System.Linq;  
  7. using System.Net.Mail;  
  8. using System.Text;  
  9. using System.Threading;  
  10. using System.Threading.Tasks;  
  11. using System.Windows;  
  12. using System.Windows.Controls;  
  13. using System.Windows.Input;  
  14.   
  15. namespace Microsoft.MailNotifier  
  16. {  
  17.     public class NotifierToolWindowViewModel  
  18.     {  
  19.         private string _email;  
  20.         private DelegateCommand signIn;  
  21.         private IVsStatusbar bar;  
  22.         private ImapClient client;  
  23.         private AutoResetEvent reconnectEvent = new AutoResetEvent(false);  
  24.   
  25.         public NotifierToolWindowViewModel()  
  26.         {  
  27.             signIn = new DelegateCommand(SignIn);  
  28.         }  
  29.   
  30.         private void SignIn(object obj)  
  31.         {  
  32.             PasswordBox pwBox = obj as PasswordBox;  
  33.             System.Threading.Tasks.Task.Run(() =>  
  34.             {  
  35.                 try  
  36.                 {  
  37.                     while (true)  
  38.                     {  
  39.                         DisplayAndShowIcon("Connecting...", (short)Microsoft.VisualStudio.Shell.Interop.Constants.SBAI_Build);  
  40.                         InitializeClient(pwBox);  
  41.                         DisplayMessage("Connected");  
  42.                         reconnectEvent.WaitOne();  
  43.                     }  
  44.                 }  
  45.                 finally  
  46.                 {  
  47.                     if (client != null)  
  48.                         client.Dispose();  
  49.                 }  
  50.             });  
  51.   
  52.              
  53.         }  
  54.         public ICommand SignInCommand  
  55.         {  
  56.             get { return signIn; }  
  57.         }  
  58.         public string Email  
  59.         {  
  60.             get { return _email; }  
  61.             set { _email = value; }  
  62.         }  
  63.           
  64.         private IVsStatusbar StatusBar  
  65.         {  
  66.             get  
  67.             {  
  68.                 if (bar == null)  
  69.                 {  
  70.                     bar = Package.GetGlobalService(typeof(SVsStatusbar)) as IVsStatusbar;  
  71.                 }  
  72.   
  73.                 return bar;  
  74.             }  
  75.         }  
  76.   
  77.         private void InitializeClient(PasswordBox pwBox)  
  78.         {  
  79.             // Dispose of existing instance, if any.  
  80.             if (client != null)  
  81.                 client.Dispose();  
  82.             client = new ImapClient("imap.gmail.com", 993, _email, pwBox.Password, AuthMethod.Login, true);  
  83.             // Setup event handlers.  
  84.             client.NewMessage += client_NewMessage;  
  85.             client.IdleError += client_IdleError;  
  86.         }  
  87.         private void client_IdleError(object sender, IdleErrorEventArgs e)  
  88.         {  
  89.             DisplayMessage("An error occurred while idling - ");  
  90.             DisplayMessage(e.Exception.Message);  
  91.             reconnectEvent.Set();  
  92.         }  
  93.   
  94.         private void client_NewMessage(object sender, IdleMessageEventArgs e)  
  95.         {  
  96.             MailMessage msg = client.GetMessage(e.MessageUID);  
  97.             DisplayMessage("Got a new message!" + " From - " + msg.From  +" Subject - " + msg.Subject + " Priority - " + msg.Priority);  
  98.         }  
  99.           
  100.   
  101.         /// <summary>  
  102.         /// Displays the message.  
  103.         /// </summary>  
  104.         public void DisplayMessage(string msg)  
  105.         {  
  106.             int frozen;  
  107.   
  108.             StatusBar.Clear();  
  109.             StatusBar.IsFrozen(out frozen);  
  110.             StatusBar.SetText(msg);  
  111.             StatusBar.FreezeOutput(0);  
  112.   
  113.         }  
  114.         /// <summary>  
  115.         /// Display message and show icon  
  116.         /// </summary>  
  117.         /// <param name="message"></param>  
  118.         /// <param name="iconToShow"></param>  
  119.         public void DisplayAndShowIcon(string message, object iconToShow)  
  120.         {  
  121.             object icon = (short)iconToShow;  
  122.   
  123.             StatusBar.Animation(1, ref icon);  
  124.             StatusBar.SetText(message);  
  125.             Thread.Sleep(3000);  
  126.   
  127.             StatusBar.Animation(0, ref icon);  
  128.             StatusBar.FreezeOutput(0);  
  129.             StatusBar.Clear();  
  130.         }  
  131.   
  132.           
  133.   
  134.   
  135.     }  
  136.     public class DelegateCommand  - ICommand  
  137.     {  
  138.         private Action<object> _executeMethod;  
  139.         public DelegateCommand(Action<object> executeMethod)  
  140.         {  
  141.             _executeMethod = executeMethod;  
  142.         }  
  143.         public bool CanExecute(object parameter)  
  144.         {  
  145.             return true;  
  146.         }  
  147.         public event EventHandler CanExecuteChanged;  
  148.         public void Execute(object parameter)  
  149.         {  
  150.             _executeMethod.Invoke(parameter);  
  151.         }  
  152.     }  
  153. }  

We have used AutoResetEvent that will notify a waiting thread that an event has occurred.

That’s all folks. We have assembled every piece and it’s time to run the application and test our VS Package. Rebuild the solution and run the application.

  1. Experimental Instance of Visual Studio will be open.
  2. Go to View->Other Windows->Notifier.
  3. Set your email and password in ToolWindow to SignIn.
  4. Close the Tool Window if you want.
  5. Wait for new mail.
  6. New mail notification will be popped in the status bar.


Conclusion

We have created our own extension for Visual Studio and with minimum effort that’s the little thing we achieved today, but with Visual Studio Extensibility, sky is the limit.

There is lot in here to improve in this code and application. For example, we can implement Google’s API to get email notifications which will be more secure and standard. We can also play with different methods provided by S22.Imap library to get particular mail or search etc. We can also improve UI a bit more and also change default icons of tool window and menus. We can deploy this extension on the market to let other people to use it but these I am leaving to you to explore and improve.

Source Code

Complete source can be downloaded from my github link.