im in ur web, enriching ur code

 
 

Programmatically setting the SmtpClient pickup directory location at runtime

In my last post on using an smtp pickup directory for ASP.NET development, I explained some of the reasons to use a pickup folder instead of an SMTP server during ASP.NET development. One of the caveats of that configuration is that you have to enter the full path of the folder you want to store the application's mail in. This is fine if you're working solo, or can agree on a common folder/path use amongst your colleagues but it reduces the instant portability of your application, since you'll have the full path of the folder set in the web.config.

You can always use the configSource property for your web.config/system.net configuration element, and specify the 'per machine details' in a separate file which isn't source controlled. Not a bad idea.

However, it still doesn't solve the 'portability' issue. ASP.NET 2.0 (Visual Studio 2005) introduced the new Web Project model, where you can simply open a folder from the file system as a website and start coding. You can copy or move the folder to another location, or another machine, and open the folder in VS2005, and once again you're away.

With that in mind, configuring the SpecifiedPickupDirectory PickupDirectoryLocation as an absolute path (which is required by the framework) isn't the best solution as it ties the mail drop folder to a fixed path and you'll have to reconfigure if you move your project.

Changing the mailSettings through the configuration model

It's not entirely straight forward to change the location of the pickup folder at runtime. Initially, I thought of using the frameworks built-in functionality to change the application's mailSettings. Which looks something like this: 

Configuration config = WebConfigurationManager.OpenWebConfiguration( "~/web.config" );

MailSettingsSectionGroup mail = (MailSettingsSectionGroup)config.GetSectionGroup( "system.net/mailSettings" );

if( mail.Smtp.DeliveryMethod == SmtpDeliveryMethod.SpecifiedPickupDirectory )

{

  string path = Path.Combine( HttpRuntime.AppDomainAppPath, @"..\Mail" );

  mail.Smtp.SpecifiedPickupDirectory.PickupDirectoryLocation = path;

}

But on closer inspection of the SmtpClient class using Reflector, you'll find that the default smtp settings utilise a static instance of an internal class called MailSettingsSectionGroupInternal, and is initialised from the default web.config settings. So changes to the runtime configuration don't effect the actual values the SmtpClient uses. That pretty much means the settings can only be 'set' when the configuration is loaded.

The only way we can get our changes to be applied is to save the configuration (with a call to config.Save()), which means writing to the web.config file. This in turn triggers the application to reload the changes. It's not ideal, since the app will be restarted and it's bad because it updates the web.config file with the new absolute path to the pickup folder. It's pretty much back to square one.

Reflection to the rescue

In .NET, nothing is too far from our reach if you know how to go about it. To programmatically set the PickupDirectoryLocation at runtime, all we need to do is use reflection to navigate our way down a path of interal classes and properties, to the single private variable that needs to change, and then simply set it.

The hierarchy that we need to change looks like this: System.Net.Mail.SmtpClient.MailConfiguration.Smtp.SpecifiedPickupDirectory.PickupDirectoryLocation

Since we can access the public member SmtpClient, we'll start there and get the first internal (and static) property, MailConfiguration. This should be called or defined in the Application_Start method of your Global.asax.

First, let's define some variables for working with, as well as the path we want to use for our drop folder. In this instance, I've set the path to the folder above the current application root, in a folder called "Mail".

BindingFlags instanceFlags = BindingFlags.Instance | BindingFlags.NonPublic;

PropertyInfo prop;

object mailConfiguration, smtp, specifiedPickupDirectory;

string path = Path.Combine( HttpRuntime.AppDomainAppPath, @"..\Mail" );

Then get the first static property and object from the SmtpClient

// get static internal property: MailConfiguration

prop = typeof( SmtpClient ).GetProperty( "MailConfiguration", BindingFlags.Static | BindingFlags.NonPublic );

mailConfiguration = prop.GetValue( null, null );

Continue down the hierarchy from the object we just assigned to mailConfiguration, getting the properties, and then the value, for each instance.

// get internal property: Smtp

prop = mailConfiguration.GetType().GetProperty( "Smtp", instanceFlags );

smtp = prop.GetValue( mailConfiguration, null );

 

// get internal property: SpecifiedPickupDirectory

prop = smtp.GetType().GetProperty( "SpecifiedPickupDirectory", instanceFlags );

specifiedPickupDirectory = prop.GetValue( smtp, null );

Lastly, get the field we want to set, as the corresponding property doesn't provide a setter.

// get private field: pickupDirectoryLocation, then set it to the supplied path

FieldInfo field = specifiedPickupDirectory.GetType().GetField( "pickupDirectoryLocation", instanceFlags );

field.SetValue( specifiedPickupDirectory, path );

And now, whenever an instance of SmtpClient is created, it will be initialised with the new pickup folder path.

A C# helper class, for your convenience.

So now you know how to set the property, here is the full listing of the helper class I use to achieve it.

How to use the CSharpVitamins.MailHelper class.

void Application_Start(object sender, EventArgs e)

{

  // set drop folder for mail

  if( MailHelper.IsUsingPickupDirectory )

    MailHelper.SetRelativePickupDirectoryLocation( @"..\Mail" );

}

Source code for the CSharpVitamins.MailHelper class.

using System;

using System.Configuration;

using System.Web;

using System.IO;

using System.Reflection;

using System.Net.Mail;

using System.Net.Configuration;

 

namespace CSharpVitamins

{

  public static class MailHelper

  {

    static bool? _isUsingPickupDirectory;

 

    /// <summary>

    /// Gets a value to indicate if the default SMTP Delivery

    /// method is SpecifiedPickupDirectory

    /// </summary>

    public static bool IsUsingPickupDirectory

    {

      get

      {

        if( !_isUsingPickupDirectory.HasValue )

        {

          Configuration config = WebConfigurationManager.OpenWebConfiguration( "~/web.config" );

          MailSettingsSectionGroup mail = (MailSettingsSectionGroup)config.GetSectionGroup( "system.net/mailSettings" );

          _isUsingPickupDirectory = mail.Smtp.DeliveryMethod == SmtpDeliveryMethod.SpecifiedPickupDirectory;

        }

        return _isUsingPickupDirectory.Value;

      }

    }

 

    /// <summary>

    /// Sets the default PickupDirectoryLocation for the SmtpClient.

    /// </summary>

    /// <remarks>

    /// This method should be called to set the PickupDirectoryLocation

    /// for the SmtpClient at runtime (Application_Start)

    ///

    /// Reflection is used to set the private variable located in the

    /// internal class for the SmtpClient's mail configuration:

    /// System.Net.Mail.SmtpClient.MailConfiguration.Smtp.SpecifiedPickupDirectory.PickupDirectoryLocation

    ///

    /// The folder must exist.

    /// </remarks>

    /// <param name="path"></param>

    public static void SetPickupDirectoryLocation( string path )

    {

      BindingFlags instanceFlags = BindingFlags.Instance | BindingFlags.NonPublic;

      PropertyInfo prop;

      object mailConfiguration, smtp, specifiedPickupDirectory;

 

      // get static internal property: MailConfiguration

      prop = typeof( SmtpClient ).GetProperty( "MailConfiguration", BindingFlags.Static | BindingFlags.NonPublic );

      mailConfiguration = prop.GetValue( null, null );

 

      // get internal property: Smtp

      prop = mailConfiguration.GetType().GetProperty( "Smtp", instanceFlags );

      smtp = prop.GetValue( mailConfiguration, null );

 

      // get internal property: SpecifiedPickupDirectory

      prop = smtp.GetType().GetProperty( "SpecifiedPickupDirectory", instanceFlags );

      specifiedPickupDirectory = prop.GetValue( smtp, null );

 

      // get private field: pickupDirectoryLocation, then set it to the supplied path

      FieldInfo field = specifiedPickupDirectory.GetType().GetField( "pickupDirectoryLocation", instanceFlags );

      field.SetValue( specifiedPickupDirectory, path );

    }

 

    /// <summary>

    /// Sets the default PickupDirectoryLocation for the SmtpClient

    /// to the relative path from the current web root.

    /// </summary>

    /// <param name="path">Relative path to the web root</param>

    public static void SetRelativePickupDirectoryLocation( string path )

    {

      SetPickupDirectoryLocation( HttpRuntime.AppDomainAppPath, path );

    }

 

    /// <summary>

    /// Sets the default PickupDirectoryLocation for the SmtpClient.

    /// </summary>

    /// <remarks>

    /// This is a shortcut for passing in two paths, which are then

    /// combined to set the pickup directory.

    /// </remarks>

    /// <param name="path1">Base path</param>

    /// <param name="path3">Relative path to be combined with </param>

    public static void SetPickupDirectoryLocation( string path1, string path3 )

    {

      SetPickupDirectoryLocation( Path.Combine( path1, path3 ) );

    }

  }

}

Click here to download the source code for the MailHelper class.

Conclusion

In summary, it appears that we need to jump through a few hoops to achieve something fairly small in nature. Although encapsulating our code into a helper class improves the process of applying our desired setting, I couldn't find anything more straight forward to accomplish the task.

kick it on DotNetKicks.com

Posted on Wednesday, December 19, 2007 6:23 PM
Filed Under [ C#, Utilities, Tips, .NET, ASP.NET, Source Code ]

Comments

Gravatar
# re: Programmatically setting the SmtpClient pickup directory location at runtime
Posted by Marc Collet
Posted on 12/20/2007 11:05 AM
I don't know about all of you but I really like things working for me..not the other way around....so...why not use the openSMTP openSource Project? This is a very good implementation of the SMTP RFC and it has worked many years for me know. If anybody is interested in the source please let me know!

Best regards,

Marc Collet
Gravatar
# re: Programmatically setting the SmtpClient pickup directory location at runtime
Posted by Dave Transom
Posted on 12/20/2007 11:42 AM
Hi Marc,

The main advantage for using a drop folder during development is that the email your application sends are ALL stored in one place. They aren't delivered via SMTP to the actual recipients, and apart from setting the drop folder relative to the current application, which this article covers, you don't have to perform any special handling; you simply look in the drop folder and see the email exactly as the real recipient would. Great for debugging.

openSMTP might be a good choice for a production server, but as far as application development is concerned, all I'm interested in is the creating and sending of mail (and then being able to verify that it sent in the development drop folder), the actual SMTP server used is usually what your hosting provider has set up.
Gravatar
# re: Programmatically setting the SmtpClient pickup directory location at runtime
Posted by David Roberts
Posted on 12/20/2007 4:01 PM
And again, thank you very much.
Gravatar
# re: Programmatically setting the SmtpClient pickup directory location at runtime
Posted by Dave Transom
Posted on 12/20/2007 6:28 PM
I'm glad you found it helpful :)
Gravatar
# re: Programmatically setting the SmtpClient pickup directory location at runtime
Posted by ashjg
Posted on 9/22/2009 11:26 PM
Hi ,

I get the System.Net.Mail.SmtpException: Cannot get IIS pickup directory.
at System.Net.Mail.IisPickupDirectory.GetPickupDirectory() excepton when i use the iis pick up directory method.

I need to send mails to about 3000 users.
Also have a default smtp server,
Can anybody help me with this.

Thanks
Gravatar
# re: Programmatically setting the SmtpClient pickup directory location at runtime
Posted by Dave Transom
Posted on 9/22/2009 11:53 PM
At a guess, I'd say you're using "PickupDirectoryFromIis" instead of "SpecifiedPickupDirectory" in your web.config.

Check your smtp settings, it should look like this:
<smtp deliveryMethod="SpecifiedPickupDirectory" ...
Gravatar
# re: Programmatically setting the SmtpClient pickup directory location at runtime
Posted by Dave Transom
Posted on 9/23/2009 12:09 AM
Actually, I may have misunderstood.

If you are purposely trying to use IIS for the pickup - it could be a permissions or simply a setup problem. It looks like the the System.Net.Mail.IisPickupDirectory.GetPickupDirectory() uses the IIS Metabase to find the directory. I'd investigate there.
Gravatar
# re: Programmatically setting the SmtpClient pickup directory location at runtime
Posted by Shashi
Posted on 2/25/2011 3:54 PM
Can these mails be tracked?
Gravatar
# re: Programmatically setting the SmtpClient pickup directory location at runtime
Posted by Micke
Posted on 2/11/2012 6:25 AM
Hi Dave,
big thnx, very useful for unittesting.
Gravatar
# re: Programmatically setting the SmtpClient pickup directory location at runtime
Posted by Giorgio
Posted on 9/1/2012 5:07 AM
This has been very useful. Very nice for testing in isolation email creation code.

Cheers,
Giorgio
Gravatar
# re: Programmatically setting the SmtpClient pickup directory location at runtime
Posted by Atina
Posted on 6/11/2013 1:30 AM
Hi,

is there a possibility of reading out the "Badmail", "Drop", "Pickup" and "Queue" paths, used by the smtp virtual server using c#?

Thanks,
Atina
Gravatar
# re: Programmatically setting the SmtpClient pickup directory location at runtime
Posted by Dave Transom
Posted on 6/11/2013 10:43 AM
@Atina: That's IIS configuration as opposed to ASP.NET config. SMTP itself has no concept of those folders.

You may be able to get that data using WMI. Perhaps start a stack overflow question if all else fails.
Gravatar
# re: Programmatically setting the SmtpClient pickup directory location at runtime
Posted by Atina
Posted on 6/11/2013 5:43 PM
Hello Dave,

Thanks for the quick reply and the tip. I'll adjust my search.

Cheers,
Atina

Post Comment

Title *
Name *
Email
Url
Comment *  
Please add 1 and 6 and type the answer here:

Recently on C# Vitamins...

Powered By Subtext

 

About C# Vitamins

Dave has been working in the industry for around 14 years, and has a focus on Javascript, C#, ASP.NET and SQL Server web development; not to mention being a standards driven type of guy.

C# Vitamins is the result of his findings while working in the web industry and a desire to share with the community; and if it was traced back far enough, you might say it might not have existed if he hadn't taken such an interest in id Software's original Quake.

Related Links

Below is a list of related links of Dave's other sites.