I'm coding a simple little class with a single method send an email. My goal is to implement it in a legacy Visual Basic 6 project, exposing it as a COM object via the COM Interop facility.
There's a detail I'm finding difficult to resolve, that being how granular should I be at validating parameters. On that light, a thing I'm really not happy about, and which is not a detail at all, is the way I'm actually handling exceptions:
public class MyMailerClass
{
#region Creation
public void SendMail(string from, string subject, string to, string body)
{
if (this.IsValidMessage(from, subject, to, body)) // CS1501
{
MailMessage msg = new MailMessage();
msg.IsBodyHtml = true;
msg.From = new MailAddress(from);
msg.To.Add(to);
msg.Subject = subject;
msg.Body = body;
SmtpClient srv = new SmtpClient("SOME-SMTP-HOST.COM");
srv.Send(msg);
}
else
{
throw new ApplicationException("Invalid message format.");
}
}
#endregion Creation
#region Validation
private bool IsValidMessage(string from, string subject, string to, string body)
{
Regex chk = new Regex(@"(\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,6})");
if (!chk.IsMatch(from))
{
return false;
}
if (!chk.IsMatch(to))
{
return false;
}
if (!string.IsNullOrEmpty(subject))
{
return false;
}
if (!string.IsNullOrEmpty(body))
{
return false;
}
else
{
return true;
}
}
#endregion Validation
}
Any suggestion will be much appreciated, so thanks much in advance for all your comments!
Note: Would it be convenient to implement Enterprise Library's Validation Application Block on this particular case?
Consider the contract that you are imposing upon the callers of SendMail. They are required to pass you a "valid email address". Who decides what is valid? SendMail does. Basically your method is "high maintenance" -- it wants things exactly the way it likes, and the only way to tell whether what you're going to give it will be satisfactory is to try and hope for the best.
Don't write high-maintenance methods without giving the caller a chance to know how to satisfy it, or at least have a way to avoid the exception. Extract the validation logic to an "IsValidAddress" method that returns a Boolean. Then have your SendMail method call IsValidAddress and throw if it is invalid.
You get several nice effects from this change:
(1) Increased separation of concerns. SendMail's job is to make the email mechanism work, not to pass judgment on whether an email address is valid. Isolate that policy decision to code that specializes in verification.
(2) Address validation is a useful tool in of itself; there are lots of times when you want to know whether an address is well-formed without sending mail to it.
(3) You can update and improve your validation logic easily because it is all in one sensible place.
(4) Callers have a way that they can guarantee that no exception will be thrown. If a caller cannot call a method without guaranteeing that the arguments are valid, then they have to catch the exception. Ideally you should never make a caller have to handle an exception to make their code correct; there should be a way they can write correct code that never throws, even if the data they've been handed is bad.
Here are a couple of articles I've written on this subject that you might find helpful:
Exception handling: http://ericlippert.com/2008/09/10/vexing-exceptions/
High-maintenance methods: http://blogs.msdn.com/ericlippert/archive/2008/09/08/high-maintenance.aspx
Having two throw
statements in a row makes no sence - only the first one will be executed and then control will be passed to the exception handler and never to the second throw
.
In my opinion it is more than enough to just say smth like "Sender e-mail is invalid." An e-mail is quite simple and short and so the user will be able to resolve this without any additional guidance.
I also think it would be better to first check all the passed in values and only then start work. What's the point in partially doing work if you can then encounter an invalid parameter value and throw an exception and never complete this work. Try to indicate errors as early as you can - at the very beginning if possible.
And:
Use
string.IsNullOrEmpty(subject)
rather than
subject == null
for checking if your strings are empty.