views:

429

answers:

5

I'm relatively new to C# and .NET and I'm trying to learn how to better handle exceptions in my code.

Take the following function I've written for example:

  public void SendEmail(string SenderEmail, string SenderDisplayName, IEnumerable<string> RecipientEmails, string Subject, string Message)
    {
        MailMessage message = new MailMessage();

        message.From = new MailAddress(SenderEmail, SenderDisplayName);
        foreach (var recipient in RecipientEmails)
        {
            message.To.Add(recipient);
        }
        message.Subject = Subject;
        message.Body = Message;

        SmtpClient smtpClient = new SmtpClient("192.168.168.182");
        smtpClient.Send(message);
    }
}

If you attempt to add an email address that is malformed in Message.From or Message.To, it will throw an exception. Right now my app is just crashing and burning when this happens.

Can someone show me the appropriate way to handle that exception in this method?

+16  A: 

This is the appropriate way to handle exceptions!

In general, an exception should not be handled unless the problem can be corrected, and it should only be handled in a place where the correction can be applied.

For example, the caller of your code might want to prompt the user to correct the bad email address. But your code can't know the right way to prompt. Are you being called from WinForms or Web Forms? What should the dialog box look like? Should there even be a dialog box? These are things that can only be known by the caller of your method, and not by your method itself.


In the caller:

try
{
    SendEmail(SenderEmail, SenderDisplayName, RecipientEmails, Subject, Message);
}
catch (MyMailAddressException ex)
{
    MessageBox.Show(ex.Message);
}

Note that any exceptions other than MyMailAddressException will propagate to code that knows how to handle them.


Appropriate level of "handling" in your method:

public enum MailAddressType
{
    Sender,
    Recipient
}

public class MyMailAddressException : Exception
{
    public MailAddressType AddressType { get; set; }
    public string EmailAddress { get; set; }

    public MyMailAddressException(
        string message,
        MailAddressType addressType,
        string emailAddress,
        Exception innerException) : base(message, innerException)
    {
        AddressType = addressType;
        EmailAddress = emailAddress;
    }
}

public void SendEmail(
    string senderEmail,
    string senderDisplayName,
    IEnumerable<string> recipientEmails,
    string subject,
    string message)
{
    using (
        var mailMessage = new MailMessage
                          {
                              Subject = subject, 
                              Body = message
                          })
    {
        try
        {
            mailMessage.From = new MailAddress(
                senderEmail, senderDisplayName);
        }
        catch (FormatException ex)
        {
            throw new MyMailAddressException(
                "Invalid from address", MailAddressType.Sender,
                senderEmail, ex);
        }

        foreach (var recipient in recipientEmails)
        {
            try
            {
                mailMessage.To.Add(recipient);
            }
            catch (FormatException ex)
            {
                throw new MyMailAddressException(
                    "Invalid to address", MailAddressType.Recipient,
                    recipient, ex);
            }
        }

        var smtpClient = new SmtpClient("192.168.168.182");
        smtpClient.Send(mailMessage);
    }
}

The caller can then catch MyMailAddressException and have all the information necessary to tell the user what to fix. Other exceptions should propagate.


My previous edits have addressed your question about the method. I have been assuming that your application has appropriate top-level exception handling. Gabriel points out to me that if you had appropriate top-level exception handling, then your application would not be crashing!

However, crashing is not necessarily a bad thing. If something happens that your code cannot handle, then crashing is the right thing to do. The alternative is to try to continue running, hoping that this unhandled exception hasn't damaged your program in such a way that it begins to produce incorrect results.

The specifics of exactly where to put "top-level handlers" depends on your program. It's different between WinForms and ASP.NET applications, for instance. However, the concept will be the same: safely log all the available information, then allow the exception to propagate, crashing the application.

Of course, you should be using finally blocks to clean up your application, even in the presence of exceptions.

John Saunders
No, I'm not ignoring it. Did you see where I talk about prompting the user to correct the bad email address? That's handling the exception.
John Saunders
No, since this is a bottom-level utility method, the exceptions should most definitely _not_ be handled here. Leave it to the callers.
John Saunders
@Jon: No, his middle paragraph perfectly summarizes the way exceptions were intended to be used: iff the issue can be dealt with.
Adam Robinson
@Jon: I gave him a verbal description of how to handle it at a higher level. I'll add a code example.
John Saunders
@John Saunders, should I catch and rethrow a different exception in this method, or simply let the exception happen on its own?
Gunblade
Only catch and rethrow if you have something to add. I'll add another example.
John Saunders
The idea is that your SendEmail method does not have to know in what context it is called. For example, if you call it in a GUI application, the right thing is to catch the exception in the calling Form and show a message box. In a command line utilty, for example, the right thing might be to write a message to Console.Error. And in a service, you might put the message into a system log. Your SendEmail method keeps beeing reusable for all of these scenarions when you do not catch the exception there within.
Doc Brown
+1. However, I typed this into snippet, is MailException an existing exception? I don't see it and I didn't see it on MSDN.
Simucal
@Simulcal: good catch. I fixed the exception being used.
John Saunders
@John: For the most part, I agree with you, and I like your answer. There's one thing missing - I don't believe that any exception should break the entire application. Your answer does not handle (or manage) exceptions outside of missing or invalid addresses. What happens in your application when the server is unavailable? The application crashes!!
Gabriel McAdams
@Gabriel: this is just one method. His application should have appropriate top-level exception handlers. Those handlers should probably log the exception - then crash the application, since it's clear that something happened that the application does not know how to handle. This is much better than keeping the application running in an unknown state, possibly accumulating damage. There are worse things than having the application (safely) crash.
John Saunders
@Gabriel: if the server is unavailable, then his caller should handle that, not `SendEmail`. His caller should decide whether to communicate with the user (and how to communicate with the user), or to log the exception and retry, or whatever. A utility method should not make those decisions on behalf of its callers.
John Saunders
@John: The poster isn't only asking about this method. He/She is also asking about the USE of this method. Its important to point out that there should be exception handling for things other than what you mention.
Gabriel McAdams
A: 

There are a number of possible solutions here, and which you choose is largely dependent upon what you want to happen.

What you need is a Try/Catch handler wrapped around the functionality in question in order to catch the thrown exception and deal with it.

Now, the thing is that you can wrap it all in an exception, and stop the email altogether, or you can do it on a per TO ADDRESS basis so that 1 bad address in a list of 100 wouldn't break the system. OR you could place it elsewhere (say where you call the function)

Stephen Wrighton
+4  A: 

You shouldn't handle the exception. You should handle (sanitize) the input and make sure that the email addresses aren't malformed according to the requirements of the MailAddress Class

here is a very basic example:

public bool IsValidEmailAddress(string EmailAddress){
    Regex regEmail = new Regex(@"^[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]$");

    if(regEmail.IsMatch(EmailAddress))
        return true;

    return false;
}

if (IsValidEmailAddress(SenderMail)){
    //do stuff to send the mail
}else{
    //return an error to the user interface
}
Russ Bradberry
This is actually a lot harder than it seems. One of the best ways to check for a proper email address is to try to create a MailAddress and catch the exception.
Jon B
You're saying he should just stop letting bad data be fed to this method? Sometimes that's not possible. Sometimes data is generated by human beings through user interfaces that are not under our control... and then we have to deal with malformed or inadequate arguments - somewhere... And if there is more than one place that calls this methoid, this method is the last line of defense, and the last opportunity to deal with them.
Charles Bretana
See here: http://stackoverflow.com/questions/201323/what-is-the-best-regular-expression-for-validating-email-addresses
Jon B
Because there's likely to be more than one place that calls this method, this method _cannot_ be the last line of defense. This method must permit callers to handle the exception appropriately.
John Saunders
i don't quite understand. The idea behind sanitizing the incoming data is to ensure that the exception doesn't get thrown. By no means should this be wrapped in a try-catch block. The last line of defense IS the exception thrown and a custom error page should do the trick. The sanitization of input just tries to mitigate this.
Russ Bradberry
@Jon B, this was just a very basic example of validating an email input. I am well aware that there are much better ways of doing it, but invoking an error is never a good idea.
Russ Bradberry
@Russ - the point of the other question I linked to is that it is so complex the best thing to do is just try it and then handle the exception.
Jon B
-1 - You should handle exceptions. What if the smtp server was unreachable, for example?
Gabriel McAdams
@Jon B: "handle it", how? The only sensible thing is to wrap it in a nicer exception, then rethrow.
John Saunders
@Gabriel: exactly. What if it was unreachable. What do you propose his code do about that? Since it can't fix the problem, it should not handle the exception.
John Saunders
@John: I would replace the RegEx code that russ posted with a try/catch around the MailAddress class and return false if there is an exception. RegEx for email address is just too complicated.
Jon B
@John: Good point. "Handle" is the wrong wording. I guess I should have said "manage." At this point, when there is an error, the developer should at least tell the user.
Gabriel McAdams
-1! I think this is a very bad solution, it duplicates functionality that is obviously already in the MailAddress constructor. And if the MailAddress constructor has a different validity check, this can lead to wrong code. Jon B's suggestion is much(!) better.
Doc Brown
Hence the reason I said to ensure that it validates according to the specification of the MailAddress Class. That being said, it is more overhead to throw an error than it is to check a string. Should I just try...catch my SQL injection attempts too?
Russ Bradberry
@Russ - easier said than done.
Jon B
@Russ: That depends entirely on what you mean by "check a string". It also depends on the *consequences*. The consequences of executing injected SQL are obviously much more dire than the consequences of instantiating a `MailAddress` object with an invalid string. But, of course, you know that, you're just trying to be difficult.
Adam Robinson
@Russ: if you duplicate functionality, you will easily introduce errors in your code when the code is changed later. If the mail validation changes later on one side, one will most likely forget to change the validation on the other side.
Doc Brown
ok, but rather than putting a try...catch on the sendmail portion, why not accept a MailAddress as an input parameter then the exception will be thrown before the method even gets called.
Russ Bradberry
@Russ: I would recommend against that, as it makes `MailAddress` part of the API, instead of a hidden implementation detail.
John Saunders
+3  A: 

The best approach is to make sure that the input to SendMessage() is correctly formatted in a way that won't cause exceptions to be thrown in the first place. Do some validation and error checking.

In any case, though, if you were going to handle it, you wouldn't probably wouldn't handle it in SendMessage. Instead, go one level up:

public void Submit() {
  try {
    SendMessage(emailForm.SenderAddress, emailForm.UserDisplayName,
      emailForm.RecipientEmails, emailForm.Subject, emailForm.MessageBody);
  } catch (MailException e) { // Substitute whichever exception is appropriate to catch here.
    // Tell user that submission failed for specified reasons.
  }
}
John Feminella
+6  A: 

Each method should only catch exceptions they can actually handle. I can't see how your SendMail method can possible do anything meaningful with an invalid mail address and thus it should just let the exception propagate to the caller.

Brian Rasmussen