|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Contents
IntroductionSendSmtp is a tiny command line utility which uses .NET's There is another very similar utility on CodeProject, Mike Ellison's Command Line EMailer.
BackgroundSome time back, a colleague created a daily build process using FinalBuilder. As well as building the executable, it also tested the SQL deployment scripts on a recent copy of each customer's database, and saved the results to an output file for each database. This created a need to extract error messages from these SQL output files. Ideally, these errors should only be e-mailed to the developers who had created the scripts with the errors. My solution was to write three general purpose command line utilities, then use these to generate and e-mail a personalized error report to each affected developer. The three utilities are:
I have now posted separate articles on each of these utilities. A final article will be posted shortly, which will demonstrate the entire error reporting process in action. Using the codeStructure of the applicationSendSmtp has been written as an N-tier application, where N < PI / 3. Here's the class diagram...
Command line switchesThe command line utility uses a hyphen followed by a single character to specify a command line switch. The related setting can either be appended to the switch, or passed as the next command line argument. For example: -fsomeone@somewhere.com
or -f someone@somewhere.com
To view the list of command line options, run SendSmtp without any parameters or with the -? switch. Below is a list of all the command line switches:
Customizing the config fileYour SMTP host might require additional parameters, such as a user name and password. SendSmtp doesn't have switches to support these parameters directly. What you can do instead is to create a config file, SendSmtp.exe.config, and add a section similar to the following: <configuration>
<system.net>
<mailSettings>
<smtp ...>
<network>
...
</network>
</smtp>
</mailSettings>
</system.net>
</configuration>
You can then customize this section to set those extra parameters. For more information on this, look up the Alternative designsIntroductionOne of the significant advantages of submitting code to CodeProject is that it inspires one to look at one's code far more critically. So, I'd like to discuss some of the alternative designs I considered, and why I chose the design I did. Design 1: Everything in Program.csInitially, all the code was in Program.cs. Separate local variables were declared for each of the command line options. As a result, most of the code was in the This was very quick, very dirty, and totally unacceptable! I soon set about refactoring it. Design 2: Moving command line option variables into their own classI then created a separate This made it much easier to refactor the large This made the code far more readable, but there were a few things that were still bothering me...
class CommandLineOptions
{
private SmtpClient client = new SmtpClient();
private MailMessage message = new MailMessage();
...
public SmtpClient Client
{
get { return client; }
}
public MailMessage Message
{
get { return message; }
}
...
}
The private static void ApplySettingToOptions(
CommandLineOptions options,
char currSwitch, string setting)
{
switch (currSwitch)
{
case 'h':
options.Client.Host = setting;
break;
case 'o':
options.Client.Port = int.Parse(setting);
break;
case 'f':
options.Message.From
= ParseAddressAndDisplayName(setting);
break;
case 't':
foreach (MailAddress address
in ParseAddresses(setting))
{
options.Message.To.Add(address);
}
break;
...
I was a bit unhappy with this design, as I felt that the instances of Additionally, although the These are fairly minor quibbles in such a small application, and in other circumstances, I would probably have ignored them. But for learning purposes, I felt it would be worthwhile to investigate some alternate designs. Design 3: CommandLineOptions becomes MessageMailerOne solution to this encapsulation problem is to remove the public accessor properties which expose the For each property of these classes which I wished to expose, I created a corresponding property of class CommandLineOptions
{
private SmtpClient client = new SmtpClient();
private MailMessage message = new MailMessage();
...
public string Host
{
get { return client.Host; }
set { client.Host = value; }
}
public int Port
{
get { return client.Port; }
set { client.Port = value; }
}
public MailAddress From
{
get { return message.From; }
set { message.From = value; }
}
public MailAddress ReplyTo
{
get { return message.ReplyTo; }
set { message.ReplyTo = value; }
}
public MailAddressCollection To
{
get { return message.To; }
}
public MailAddressCollection CC
{
get { return message.CC; }
}
...
This largely addresses the encapsulation issue. But it creates a new problem. The With this refactoring, about half the methods of the Since the responsibilities of the class have changed, its title should also be changed (and I guess it deserves a salary increase too!). I chose to rename the class to " In one sense, this felt like the right design, as there was a more even spread of responsibilities between the two classes. However, in terms of its changed responsibilities, it made no sense for static void Main(string[] args)
{
bool verboseMode = true;
bool promptToExit = false;
string errorFileName = null;
MessageMailer mailer = new MessageMailer();
...
... replaced ... static void Main(string[] args)
{
CommandLineOptions options = new CommandLineOptions();
...
Now, the code was looking messier again. For example, the call to: ParseArguments(args, options)
... became ... ParseArguments(args, mailer, out errorFileName,
out verboseMode, out promptToExit)
While So now, it felt like I was once again needing a class whose responsibility would be to store the command line options. By this point, I was getting quite frustrated. SendSmtp is a small stand-alone program. This alone makes it very easy to maintain. So it just wasn't economical to spend this much time on getting the "perfect" design! Design 4: Move MailMessage and SmtpClient out of CommandLineOptionsIt occurred to me that with design 3, I was being forced to move variables like Perhaps the better alternative was to move the After all, these are significant classes in their own right, and it makes far more sense for them to lead a separate existence than to make an orphan of So instead of: ParseArguments(args, options)
or ParseArguments(args, mailer, out errorFileName,
out verboseMode, out promptToExit)
I ended up with... ParseArguments(args, options, client, message)
This felt a lot better than the previous design! Design 5: Duplicating the properties of MailMessage and SmtpClient in CommandLineOptionsThe purest design would probably be to give There would then be no need to encapsulate I was almost ready to settle on this design, when I remembered that the default constructor for the My final choiceBecause of the technical consideration discussed above, the "pure" design 5 was not an option. I felt that design 4 was the best remaining choice. History28 November 2006
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||