Serializing a Dictionary

For better or for worse, dictionaries are not allowed in .net Web API’s.  List’s are allowed, and what’s a dictionary, if not a list of key, value pairs?  So, I wrote this extension method to “serialize” a dictionary.

public static class ExtensionMethods
{
     public static List<KeyValuePair<T1, T2>> Serialize<T1, T2> (this Dictionary<T1, T2>)
     {
          List<KeyValuePair<T1, T2>> list = new List<KeyValuePair<T1, T2>>();
          list.AddRange(dictionary);
          return list;
     }
}

Then, you call it like this:

Dictionary<int, string> wordsThatStartWithB = new Dictionary<int, string>()
{
     { 1, "Bob" },
     { 2, "Blue" },
     { 3, "Bone" },
     { 4, "Bow" }
};

return wordsThatStartWithB.Serialize();

Of course, there are probably better ways of doing this, which is why I will be open sourcing my extensions library in the coming weeks, and will have a blog post about each one.

Do you have any recommendations on improving this?

Windows Phone Error code: 85010014

The Short Version

My phone suddenly began reporting Error Code: 85010014 when attempting to sync my work email.

The Fix

It turns out I had a calendar event with a status of “Meeting Elsewhere”.  Changing the status to “Free/Busy/Tentative/Out Of Office” fixed the problem immediately.

The Long Story

Four days ago, my work email suddenly refused to sync on my phone, giving me error code 85010014.  I searched the web, over and over and over and over, to no avail (which is why I’m posting this), so figuring it out was up to me.

What made it look like a device issue:

Because my Outlook and my Web Access functioned properly, from the business side anyway, it looked as though the problem was on my device somehow.  The first thing I attempted was to change my password, but that didn’t fix the problem.

Why a device issue just didn’t feel right:

There were several reasons why I was sure it wasn’t a problem on my device.  I observed the following:

  • Restarting device didn’t fix
  • Removing and Re-Adding the account didn’t fix
  • Removing and Restarting and Re-Adding the account didn’t fix
  • Outlook.com address was working
  • School email (exchange also) was working

Then, I started reading a forum post I came across, which linked to another post, which linked to another post….anyway, I eventually made my way to a post from a problem someone experienced several years ago on their WP7 device with similar symptoms.  They fixed it by unchecking all the boxes for “Content to sync”, then syncing, then adding “Email”, then syncing, then “Contacts”, sync, “Calendar”, sync, “Tasks”, sync.  He observed that by adding the services back one-by-one, the sync was successful and his problem went away.  So I did the same thing, and noticed the same thing…until I checked the box next to Calendar.  The sync failed.  So, I was able to observe that my account would sync, as long as I didn’t attempt to sync the calendar.  So, I pulled out my old WP 7 device and sure enough, I was able to duplicate this behavior.  So, this helped me narrow down the problem, and eliminate my device. 

I kept reading about “try refreshing your cache”, and I wasn’t sure if that was IE cache, or some special device cache or what, but the Microsoft support folks in the forums kept saying, “contact your device manufacture for instructions to clear the cache”.  So, I updated to Windows Phone 8.1.  I’ve been wanting it anyway, and it was released to developers that day, so I downloaded it.  I assumed it would clear my cache, but whether it did or not, the problem persisted.

So, I added my account to my co-workers phone (another WP8, different manufacturer), and was able to duplicate the behavior.  All the while, his account continued to function properly.

So, now I’m convinced that there is a problem with my account, or my calendar or something, but no one knows what to do, and officially, my employer doesn’t support email on devices.  So, I’ve been dealing with this for four days, and tonight I decided to really dig in and try to get this figured out.

I started by looking at the properties of my calendar in Outlook.  I noticed that I’ve never cleaned my calendar since I started with the company two years ago, so it had well over 400 items.  I thought surely not, but maybe-just-maybe, I’ve reached some sort of threshold and the sync process can’t handle so many calendar items.  So, I performed an archive and removed all events older than 90 days.  Still nothing.

So, I started looking through my upcoming events and found one that had my status as “Meeting Elsewhere”, because I’m trying anything and everything I can think of at this point, I change it to “Free” and try syncing on my phone, success!  I thought maybe it just took a while for the clean-up to take effect, so I changed the status back to “Meeting Elsewhere” and sure enough, it broke again, and to fix it, just switch it back to “Free”.

Configuration Specific Reference Location

Sometimes there is the need to have references dependent upon the configuration you are currently in.  You may have a dependencies folder that all your applications can access, and you have an assembly per configuration.  So when you are debugging your application, you want to use the project reference for your API, but when you deploy to your testing server, you want to use the testing version of the API, or Staging, or Release, or…well, you get the idea.

The solution to this is quite easy.

The details of our example will be as follows:

  • One solution with two projects
    • Consumer (Console application)
    • ServiceAPI
  • Our solution will have four configurations
    • Debug
    • Test
    • Stage
    • Release
  • Debug will use a project reference
  • Test, Stage, and Release will reference C:\Dependencies\Test, (Or \Stage or \Release)

Once you have your solution set up and configured, add a reference to your ServiceAPI inside your Consumer project.  Now we have the basics setup and are ready to modify our project file to have different API paths depending on our current configuration.

First, right-click on the Consumer project and select, edit project file.  If prompted to save changes to your solution, click Yes.

image

Inside your project file you will see a series of PropertyGroups and ItemGroups.  If you notice, you have one PropertyGroup for each configuration of your project, and an ItemGroup for reference items, one for compile items, and one for project references.

We want our project reference to only be a project reference if we are in Debug mode.  If you look through your project file, you will find something like this:

  <ItemGroup>
    <ProjectReference Include="..\ServiceAPI\ServiceAPI.csproj">
      <Project>{94c972f3-2724-4258-a16f-b121d8502a27}</Project>
      <Name>ServiceAPI</Name>
    </ProjectReference>
  </ItemGroup>

Add the following Condition attribute to the ItemGroup tag.  One thing to note here is that the Condition argument syntax is the same in both Visual Basic and C#.  The comparison operator used is the C# one and you would use the same even if this is a Visual Basic project.

  <ItemGroup Condition="'$(Configuration)' == 'Debug'">
    <ProjectReference Include="..\ServiceAPI\ServiceAPI.csproj">
      <Project>{94c972f3-2724-4258-a16f-b121d8502a27}</Project>
      <Name>ServiceAPI</Name>
    </ProjectReference>
  </ItemGroup>

Now, we want the ServiceAPI to have a specific folder based on it’s configuration, so add the following ItemGroup to the XML file:

  <ItemGroup  Condition="'$(Configuration)' != 'Debug'">
    <Reference Include="ServiceAPI">
      <SpecificVersion>False</SpecificVersion>
      <HintPath>C:\Dependencies\$(Configuration)\ServiceAPI.dll</HintPath>
    </Reference>
  </ItemGroup>

The HintPath is the location Visual Studio should look for the dll, and you can use the environment variables there, so you notice the hint path includes $(Configuration).  Now you can reload the project (Right click the project and select “Reload Project”.  If prompted to save, click “Yes”.

In Debug mode, you will notice the ServiceAPI is using the project reference, but as we switch between configurations, the path changes:

Debug Mode:

image

Release Mode:

image

Test Mode:

image

Stage Mode:

image

One thing to note as you cycle through the configurations.  The properties grid doesn’t update as you change configurations, but the project does.  That means if you switch from Test to Stage, the properties window will still stay Test, but the project will be using Stage.  If you click another reference and then click back to ServiceAPI, it will update the properties window and you will see the path is actually changed.

Feel free to leave any questions or comments in the comments section below!

Happy Coding!

Send a text message using .net

Sometimes, we want to send text messages from our application.  Whether it’s for sending out a secret code for two-tiered authentication, or sending out messages to subscribed members, it’s a feature that is occasionally needed, and one that I’ve recently needed.  There are many services out there that charge a fee, either monthly or per message, but the services are out there.  I’ve also seen several open-source applications that act as an instant messaging type service, in that they have a GUI and do not provide an API.  What I was unable to find was an API that handled all carriers, and was free.  So I built one, and here is how to use it.

Your first step is to download the single DLL, which is needed to use the API.  It’s open source, so you can get the code and make any changes you want and compile the DLL yourself, if you so desire.  You can get the DLL here, and the code here.

After adding the DLL as a reference to your project, it’s actually quite simple to use.  First, add the appropriate using:

using SMSOverEmail;

After the using statement is added, we are ready to start using the API.  The first step is to instantiate a new SMSOverEmail object.

SMSOverEmail.SMSOverEmail sms = new SMSOverEmail.SMSOverEmail();

With our object instantiated, we need to set up the “From” part of our text message.  We are using SMTP to send the actual text message, so this is similar to setting up a SMTP client.

sms.EnableSsl = true;
sms.SMTPUserName = "userName@gmail.com";
sms.SMTPPassword = "password";
sms.Host = "smtp.gmail.com";
sms.Port = 587;
sms.From = new System.Net.Mail.MailAddress("userName@gmail.com");

These settings are correct if you are using Gmail, but you can use any email provider you wish, as long as you know the settings Smile

Once the “From” part is set up, we can set up the “To” part.  This is a text message, so we are delivering to a mobile device.  So, for the purpose of this API, you just need to know the 10 digit phone number, and the carrier it belongs to.  The API does the rest.

sms.PhoneNumber = "2345678901"; //a ten digit phone number
sms.RecipientCarrier = Carrier.SprintPCS;

The phone number is a string, and must be the 10 digit phone number belonging to the phone.  The Carrier enumerator has over 60 carriers to choose from.  In our example, we are sending a text message to (234)567-8901 which is held by a Sprint customer.

Now that the recipient and the sender are setup, we can send our text message.

sms.SendTextMessage("test message");

The SendTextMessage function has many different overloads, they are all documented inline, but you can see the full list on the projects CodePlex documentation page.

No tutorial would be complete without showing all of the code put together in a complete console application:

namespace SSMSTest
{
    using System;
    using SMSOverEmail;

    class Program
    {
        static void Main(string[] args)
        {
            SMSOverEmail.SMSOverEmail sms = new SMSOverEmail.SMSOverEmail();
            sms.EnableSsl = true;
            sms.SMTPUserName = "userName@gmail.com";
            sms.SMTPPassword = "password";
            sms.Host = "smtp.gmail.com";
            sms.Port = 587;
            sms.From = new System.Net.Mail.MailAddress("userName@gmail.com");

            sms.PhoneNumber = "2345678901"; //a ten digit phone number
            sms.RecipientCarrier = Carrier.SprintPCS;

            sms.SendTextMessage("test message");
        }
    }
}

The API also comes with a dictionary containing the actual carrier name, along with the enumerator.  For example, Carrier.ATT is AT&T.  Each enumerator is simple to understand, but I threw it in in case you needed a user-friendly list of carriers.  Assuming sms is the name of your SMSOverEmail object, then sms.CarrierNames is the dictionary.  It’s in the form of Dictionary<Carrier, string>, where Carrier is the enumerator, and string is the user-friendly name.

As always, I would love to hear from you.  Please leave your thoughts about this post below, especially if this post has helped you Smile  If you have any questions, I’ll be sure to do my best to answer those as well.

Problem accessing regedit after updating to Windows 8

At work, I recently upgraded to Windows 8, and I have to admit that the upgrade was very quick and painless, and, so far I really enjoy the changes.  The new start menu is taking some time to get used to, but all-in-all I enjoy it.  I recently had a reason to modify the registry, and I got the following error:

image

I thought this was odd because I am an administrator on my box.  I even added myself to the local administrators group explicitly, and chose “Run As Administrator” from the context menu, but the error message still persisted.

Here are the steps I took to fix it:

  1. Open File Explorer (at some point, Windows Explorer needed a name change?) and navigate to C:\Windows\
  2. Right click on regedit.exe and select Properties
  3. Go to the Security tab and click the Advanced button so this screen appears:
    image
  4. Click the Continue button with the shield
  5. Click the Add button and the following window appears:
    image
  6. Click “Select a principle” at the top and the following window appears:
    image
  7. Enter your network ID, click Check Names just to make sure, and then click OK
  8. Click OK, OK, Yes

Now, you can run regedit.exe as normal.

I hope this post has helped you.  Please leave a comment below if you found this post helpful.

Forcing Save As in Microsoft Word

My wife came to me a few weeks ago with a problem: When she would open a word document attached to an e-mail sent to her Yahoo account, she would sometimes lose the changes made to the document when she closed it.  Even though she clicked ‘yes’ when the program asked if she wanted to save her changes, the document was being saved to a temporary internet file that she couldn’t access once the document was closed.

There are several solutions to this problem, but my wife wasn’t particularly enthused about changing her online habits.  And, why shouldn’t she expect her document to be found after hitting the save button?  Why doesn’t MS Word recognize that the open document is in Temporary Internet Files and perform a Save As instead of a Save?  Word should, but it doesn’t, so I wrote a Word Add-In to do the trick for her.

Our solution should force a Save As if the Save function is invoked, but do not interfere if the Save As is invoked…and of course, ignore any document that is not in the Temporary Internet Files location.  I knew it would be easy, but I didn’t realize just how easy it would be. 

I’m using VS 2012 these days, but I did this in VS 2010 because we use Office 2007, and VS 2012 didn’t have (or I couldn’t find) the Office 2007 Add-In templates…so, open up VS 2010 and create a new project, choose Installed Templates –>Visual C# –>Office –>2007—>Word 2007 Add-In.

image

The template opens up with two events already wired-up, the Startup and Shutdown events.  We need to add the DocumentBeforeSave event, so first create the function:

private void Application_DocumentBeforeSave(Word.Document Doc, 
                                            ref bool SaveAsUi, 
                                            ref bool Cancel)
{
}

Doc is the document that is open.  SaveAsUi will be true if the SaveAs was invoked, false if it’s just Save.  Cancel, you can set to true if you want to cancel the save event.  One thing to remember is that neither the SaveAsUi or the Cancel properties are read-only.  You can set them to true or false; however, setting SaveAsUi to true doesn’t do anything.  It’s for your informational purposes only.  So, just remember to treat SaveAsUi as read-only, and treat Cancel as write only and you won’t get frustrated or confused.

Now, we need to add the event handler to our application.  So, put this code in the ThisAddIn_Startup function:

this.Application.DocumentBeforeSave +=
    new Word.ApplicationEvents4_DocumentBeforeSaveEventHandler(
           Application_DocumentBeforeSave);

Now, remember that we need to ignore the SaveAs event, and we have this handy variable called SaveAsUi, which tells us if we in a SaveAs event.  So, let’s start with that by adding an If statement to our DocumentBeforeSave function:

if (!SaveAsUi)
{
}

If SaveAsUi is false, then we need to check our location.  We only want to intercede if we are inside the “Temporary Internet Files” folder, so place that check inside the if statement above:

if (Doc.Path.Contains("Temporary Internet Files"))
{
}

If both conditions are true, then we need to invoke the Save As dialog box.  The only problem here is that I was not able to find a way to invoke the Save As dialog box, which means we have to make our own.   If you go to File –> Save As from just about any program, you will see a drop-down box labeled File Type.  This is populated by a property called “Filter” on the SaveFileDialog control.  If you happen to look at the Filter for MS Word 2007, you will see that there are 18 items listed.  I didn’t want to type “filter += foo” 18 times, so I created a dictionary.  We, of course, also need a SaveFileDialog control.  So first add the following using statement to your class, and then add to two properties.

using System.Windows.Forms;
private Dictionary<string, string> SaveAsFilters { get; set; }
private SaveFileDialog SaveAsDialog { get; set; }

The idea is to populate the dictionary with the filters and then just loop over the dictionary to populate the dialogs filter property.  So, inside the ThisAddIn_Startup function, include the following code.

First, populate the dictionary:

this.SaveAsFilters = new Dictionary<string, string>
{
    { "Word Document", "*.docx" },
    { "Word Macro-Enabled Document", "*.docm" },
    { "Word 97-2003 Document", "*.doc" },
    { "Word Template", "*.dotx" },
    { "Word Macro-Enabled Template", "*.dotm" },
    { "Word 97-2003 Template", "*.dot" },
    { "PDF", "*.pdf" },
    { "XPS Document", "*.xps" },
    { "Single File Web Page", "*.mht;*.mhtml" },
    { "Web Page", "*.htm;*.html" },
    { "Web Page, Filtered", "*.htm;*.html" },
    { "Rich Text Format", "*.rtf" },
    { "Plain Text", "*.txt" },
    { "Word XML Document", "*.xml" },
    { "Word 2003 XML Document", "*.xml" },
    { "OpenDocument Text", "*.odt" },
    { "Works 6.0 - 9.0", "*.wps" },
    { "Works 7.0", "*.wps" }
};

As of Word 2007, this is all the filters in Words Save-As dialog.

Even though it’s just a string, I ran into some problems appending to the Filter property, so we will create a string and append to it.  We’ll also include a counter so that we can include a pipe (“|”) character in between each filter.

int x = 1;
string filterText = string.Empty;
foreach (KeyValuePair<string, string> filter in this.SaveAsFilters)
{
    filterText += filter.Key + " (" + filter.Value + ")|" + 
                                                       filter.Value;

    //add the pipe to seperate the filters
    if (x != this.SaveAsFilters.Count())
    {
        filterText += "|";
    }
    x++;
}

this.SaveAsDialog.Filter = filterText;

We want our dialog to look and feel as much like Microsoft Word as possible, so we set the following properties:

this.SaveAsDialog.Filter = filterText;
this.SaveAsDialog.Title = "Save As";
this.SaveAsDialog.SupportMultiDottedExtensions = true;
this.SaveAsDialog.OverwritePrompt = true;
this.SaveAsDialog.InitialDirectory = Environment.GetFolderPath(
                              Environment.SpecialFolder.MyDocuments);
this.SaveAsDialog.AutoUpgradeEnabled = true;
this.SaveAsDialog.AddExtension = true;
this.SaveAsDialog.DefaultExt = "docx";

Now that we have everything in place, we can finish off our DocumentBeforeSave event by including the following code inside our nested if statement:

if (this.SaveAsDialog.ShowDialog() == DialogResult.OK)
{
    Doc.SaveAs(this.SaveAsDialog.FileName);
}

Only act if the user presses the “Save” button, and when they do call the Documents SaveAs method, passing in the filename and path that the user selected.  This will save the document, but it still doesn’t feel just right.  If I call SaveAs from any program (MS Word included), the current document is changed to reflect the newly created document.  Calling the SaveAs function in this manner does not do that, so we need some code to mimic that behavior.

Cancel = true;
this.Application.Documents.Close();
this.Application.Documents.Open(this.SaveAsDialog.FileName);

First we cancel the calling save event, then we can close the current document and then open the new document.

And that’s it, now we have created our Word Add-In.  Before I show you the code in it’s entirety, we need to walk through the publish process.  Word Add-Ins are published using ClickOnce, and because it’s integrating into Word, it requires a certification to go along with it.  I didn’t want to spend the money on a Signed Cert., so I published it without one.  It’s easy to do if you can control the computer you are installing to.

First thing is to set up ClickOnce.  Right-click on your project file in Soultion Explorer and choose Properties, and then click on the Publish Tab:

image

The first box is where the deployment files will be uploaded to.  I use ftp because I’m sending this to my website, but you could also choose to use a UNC path.  The nested box below that is where to go to download the installation files.  So, I upload to an ftp site, and then point to a webpage for the actual deployment.  In my case it’s http://www.karrapps.com/tempsaveintercept/TempSaveIntercept.vsto.

By default, the prerequisite options are set to allow the program to download and install any missing prereqs from each vendor’s web site.  I recommend leaving it this way.  If you need to change this behavior though, just click the Prerequisites button.

Your add-in will check for updates “every time the customization runs” by default.  For my purposes this isn’t really that important, so I clicked the Update button and set it to check every 1 day.  You can also select to never check for updates.

The Options buttons contains two sections; Description and Office Settings.  Here you can select a publishing language, define the publishers author, define the product name, and provide a support url.  Office settings include Solution name, and description as well as a choice between loading the Add-In at startup or on demand.

Once the settings are to your liking, just click the Publish Now button, address any prompts that come up (authentication prompts for example), and your ready for the client PC.

Here’s where we get around the cert.  This method will still prompt the user with a warning that the installation can’t be trusted, but it works and is free.  The website domain will need to be added as a trusted location, you can do that under Internet Options –> Security—>Trusted Sites—>Sites button.  So, I add http://karrapps.com as a trusted site.

Then, download and run setup.exe from the Installation Folder and that’s it.  The Add-In is automatically installed and enabled inside MS Word.

Now, the code in it’s entirety:

namespace TempSaveIntercept
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows.Forms;
    using Word = Microsoft.Office.Interop.Word;

    public partial class ThisAddIn
    {
        private Dictionary<string, string> SaveAsFilters;
        private SaveFileDialog SaveAsDialog = new SaveFileDialog();

        private void ThisAddIn_Startup(object sender, 
                                       System.EventArgs e)
        {
            this.Application.DocumentBeforeSave +=
         new Word.ApplicationEvents4_DocumentBeforeSaveEventHandler(
                                    Application_DocumentBeforeSave);

            this.SaveAsFilters = new Dictionary<string, string>
            {
                { "Word Document", "*.docx" },
                { "Word Macro-Enabled Document", "*.docm" },
                { "Word 97-2003 Document", "*.doc" },
                { "Word Template", "*.dotx" },
                { "Word Macro-Enabled Template", "*.dotm" },
                { "Word 97-2003 Template", "*.dot" },
                { "PDF", "*.pdf" },
                { "XPS Document", "*.xps" },
                { "Single File Web Page", "*.mht;*.mhtml" },
                { "Web Page", "*.htm;*.html" },
                { "Web Page, Filtered", "*.htm;*.html" },
                { "Rich Text Format", "*.rtf" },
                { "Plain Text", "*.txt" },
                { "Word XML Document", "*.xml" },
                { "Word 2003 XML Document", "*.xml" },
                { "OpenDocument Text", "*.odt" },
                { "Works 6.0 - 9.0", "*.wps" },
                { "Works 7.0", "*.wps" }
            };

            int x = 1;
            string filterText = string.Empty;
            foreach (KeyValuePair<string, string> filter in 
                                                 this.SaveAsFilters)
            {
                filterText += filter.Key + " (" + filter.Value + 
                                                 ")|" + filter.Value;

                //add the pipe to seperate the filters
                if (x != this.SaveAsFilters.Count())
                {
                    filterText += "|";
                }
                x++;
            }

            this.SaveAsDialog.Filter = filterText;
            this.SaveAsDialog.Title = "Save As";
            this.SaveAsDialog.SupportMultiDottedExtensions = true;
            this.SaveAsDialog.OverwritePrompt = true;
            this.SaveAsDialog.InitialDirectory = 
    Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
            this.SaveAsDialog.AutoUpgradeEnabled = true;
            this.SaveAsDialog.AddExtension = true;
            this.SaveAsDialog.DefaultExt = "docx";
        }

        private void ThisAddIn_Shutdown(object sender, 
                                        System.EventArgs e)
        {
        }

        private void Application_DocumentBeforeSave(Word.Document Doc,
                                                    ref bool SaveAsUi,
                                                    ref bool Cancel)
        {
            if (!SaveAsUi)
            {
                if (Doc.Path.Contains("Temporary Internet Files"))
                {
                    if (this.SaveAsDialog.ShowDialog() == 
                                                     DialogResult.OK)
                    {
                        Doc.SaveAs(this.SaveAsDialog.FileName);
                        Cancel = true;
                        this.Application.Documents.Close();
                        this.Application.Documents.Open(
                                         this.SaveAsDialog.FileName);
                    }
                }
            }
        }

        #region VSTO generated code
                …………………………
        #endregion
    }
}

Please note that I had to adjust the spacing to make everything fit on the page, but you get the idea Winking smile

Please leave your comments below.  I would love to hear from you.

Remove Recipients From Email When Not In Release Mode

Often, we need our applications to send emails to people.  When this is the case, if we are not careful, we can end up sending test emails to our external users.  We can get around this very easily by using preprocessor directives and writing our own SmtpClient class.

This isn’t a tutorial on sending email through .net, but here is a very basic function that will do that.  First the usings:

using System.Collections.Generic;
using System.Net.Mail;

Then the function:

public void sendEmail(string message, string subject, MailAddress fromAddress, 
            List<MailAddress> toAddressList, List<MailAddress> ccAddressList, 
            List<MailAddress> bccAddressList)
{
    MailMessage mail = new MailMessage();
    SmtpClient client = new SmtpClient();

    mail.From = fromAddress;
    mail.Subject = subject;
    mail.Body = message;

    foreach (MailAddress address in toAddressList)
    {
        mail.To.Add(address);
    }

    foreach (MailAddress address in ccAddressList)
    {
        mail.CC.Add(address);
    }

    foreach (MailAddress address in bccAddressList)
    {
        mail.Bcc.Add(address);
    }

    client.Host = "smtp.gmail.com";
    client.Port = 587;
    client.Credentials = new System.Net.NetworkCredential("username", "****");
    client.EnableSsl = true;

    client.Send(mail);
}

Now, that is just a generic function that will send an email.  I’m not going to bother explaining it.  See that last line though: “client.Send(mail);”?  That Send function is what we want to change.  To do that, we create a new class, with SmtpClient as the base, and then overload the Send function.

Our usings are the same:

using System.Collections.Generic;
using System.Net.Mail;

And here is our new class:

class NewSmtpClient : SmtpClient
{
    public void Send(MailMessage message)
    {
#if DEBUG
        List<MailAddress> toList = new List<MailAddress>(message.To);
        List<MailAddress> ccList = new List<MailAddress>(message.CC);
        List<MailAddress> bccList = new List<MailAddress>(message.Bcc);

        foreach(MailAddress address in toList)
        {
            if(address.Host.ToLower() != "gmail.com")
            {
                message.To.Remove(address);
            }
        }

        foreach (MailAddress address in ccList)
        {
            if (address.Host.ToLower() != "gmail.com")
            {
                message.CC.Remove(address);
            }
        }

        foreach (MailAddress address in bccList)
        {
            if (address.Host.ToLower() != "gmail.com")
            {
                message.Bcc.Remove(address);
            }
        }
#endif

        //there must be at least one recipient 
        if ((message.To.Count + message.CC.Count + message.Bcc.Count) > 0)
        {
            base.Send(message);
        }
    }
}

Of course, now that we have our new class, we need to change our calling function.  So, in our generic function we will change

SmtpClient client = new SmtpClient();

to

NewSmtpClient client = new NewSmtpClient();

So, I want to explain a few things here.  See that fifth line, #if DEBUG?  That’s a preprocessor directive.  Visual Basic has them to, but MSDN just called them directives.  Anyway, these directives are processed before the compiler runs.  Anything between #if DEBUG and #endif will not even show up in the IL Code if you compile in Release mode.

What is happening in our example class here, is that when you call client.Send(mail), if you are in DEBUG mode, then we remove any email recipient whose host is not “gmail.com”.  Of course, you would use any host that you want.  This results in a recipient list that is any of the original recipients but, no emails will be sent to “gmail.com”.  I don’t think that is worded very well, so let me visualize it for you.

  • To List
    • one@gmail.com
    • one@yahoo.com
    • one@bing.com
  • CC List
    • two@gmail.com
    • two@yahoo.com
    • two@bing.com
  • BCC List
    • three@gmail.com
    • three@yahoo.com
    • three@bing.com

After we call Send, but before the email is actually sent, the new recipient list looks like this:

  • To List
    • one@gmail.com
  • CC List
    • two@gmail.com
  • BCC List
    • three@gmail.com

The if statement at the end is because it is possible that we will end up removing all of our recipients, in which case we don’t want to send the email.  In this scenario, but without such an if statement, an exception will be thrown.

If this post has helped you in any way, please leave a comment below. Or, if you solved this method another way, please leave a comment.  I would like to hear from you!