1. Original Entry + Comments2. Write a Comment3. Preview Comment
New comments for this entry are disabled.


August 08, 2005  |  IntelliSense instead of docs  |  9774 hit(s)

One of the changes for ASP.NET 2.0 is that the class(es) for sending email messages have been enhanced. For starters, they're not in System.Web, coz that's a silly place for them to be; instead, they're in System.Net. I've also known, vaguely, that there have been additional enhancements to support functionality that was not previously available, such as embedding files (graphics), sending credentials, SSL support, et.. I thought I should look into this, because it won't be long before I'll be using 2.0 and sending emails (I assume the old classes still work, tho).

But I thought I'd start with just sending a simple email. And I thought it would be an interesting exercise to see if I could figure out how to do it without any documentation, just using the IDE -- specifically, IntelliSense and real-time compilation validation. (Since I'm a VB guy.) So here's how it went.

I knew that the message itself would be a class. So I started with Dim msg As New and then let IntelliSense help me figure out that I wanted System.Net.Mail.MailMessage. I admit this is a slightly unfair advantage over the total noob, in that I at least knew roughly what namespace to look in. Anyway, I started with:
Dim msg As New System.Net.Mail.MailMessage()
To set the From address, I typed this:
msg.From = "mike@elsewhere.com"[1]
Nope. The editor squiggles "From" and tells me "Value of type 'String' cannot be converted to System.Net.Mail.MailAddress." Aha. Addresses must be types now. I quick-like import System.Net.Mail to save some effort. Then using IntelliSense I hunt around for a likely-sounding type for addresses and end up with this:
Dim fromAddress As New MailAddress()
fromAddress.Address = "mike@elsewhere.com"
Error: Property 'Address' is Read-Only.

Hmm. Wonder why. Ok, I bet I can pass it in the constructor:
Dim fromAddress As New MailAddress("mike@elsewhere.com")
That works. Cycling through the overloads, I learn that I can also set a display name:
Dim fromAddress As New MailAddress("mike@elsewhere.com", "Mike")
I figure the To address must be similar, so I do this:
Dim msg As New MailMessage()
Dim fromAddress As New MailAddress("mike@elsewhere.com", "Mike")
Dim toAddress As New MailAddress("mike@work.com", "Mike")
msg.From = fromAddress
msg.To = toAddress
Now msg.To is squiggled: "Property 'To' is Read-Only."

Oh for heaven's sake. Why is From settable but To is not? I retrace and try overloads again, which yields this:
Dim fromAddress As New MailAddress("mike@elsewhere.com", "Mike")
Dim toAddress As New MailAddress("mike@work.com", "Mike")
Dim msg As New MailMessage(fromAddress, toAddress)
Editor does not complain. On to the rest of the message! Using hints from IntelliSense, I construct this:
msg.Body = "<b>Testing new email</b>"
msg.Subject = "Testing new email"
msg.IsBodyHtml = True
I guess that's enough actual message. I want to send my message now, and I notice the lack of two members I was expecting: a property to set the SMTP server name, and a send method. Those obviously belong to another class, so it's back to IntelliSense.

I peer through the classes in System.Net.Mail and try a few. Ultimately I decide that SmtpClient seems like the most promising, so I create this:
Dim mailSender As New System.Net.Mail.SmtpClient()
The overloads allow me to specify host and port, which makes it seem like the right thing. I now have this:
Dim mailSender As New System.Net.Mail.SmtpClient()
mailSender.Host = "(hostName)"
I look through the other members of the SmtpClient class. A property named DeliveryMethod looks like it might be useful. IntelliSense offers me three enums for this property:
SmtpDeliveryMethod.Network
SmtpDeliveryMethod.PickupDirectoryFromIis
SmtpDeliveryMethod.SpecifiedPickupDirectory
Arg. Man, I don't know. Our mail host is across the network, so that seems like a good choice. (Is it the default?) But perhaps it's an IIS-based SMTP virtual server; in that case, do I need to explictly specify a pickup directory? (Seems like that, too, would be defaulted.) Well, I'll just try sending it without setting the DeliveryMethod property and see what happens. I'm pretty sure that the SmtpClient class must have a Send method. Whoa, it sure does -- it has three of them:
Send
SendAsynch
SendAsynchCancel
I deduce (aren't I the brilliant one) that Send is synchronous. I know that asynch methods generally require creating a callback delegate or something, and although it's possible I could do that, I don't need to right now. But it's duly noted for future reference.

Curiously, the SmtpClient class has no obvious property for specifying which message to send. But once again IntelliSense rescues me and informs me that I can pass the message as an argument to the Send method.

Since I have no idea if any of this is going to work, I figure I'll put the Send method in a Try block and dump whatever errors the Catch block produces. Who knows, if it doesn't work, I might learn something. So now I have this:
Try
mailSender.Send(msg)
Label1.Text = "Message sent."
Catch ex As Exception
Label1.Text = ex.Message
End Try
Shall we try it? Do let's. Compilation works, which I expected, since there were no squiggles. But I get this error, which makes me glad I used the Try/Catch block:

Mailbox unavailable. The server response was 5.7.1. Unable to relay for (name).

I had used one of my non-work email addresses as the From address. Perhaps that's it? I change From and To to be the same (work) email address. Nope, same error.

Hmm. Hmm. A little googling to learn what the 5.7.1 error is, from which I deduce that the Exchange server is refusing to relay for anyone not in its domain. So I look again at the From and To addresses. Ok, well, one problem is that I misspelled the From address. A few more experiments, and I learn that our internal relay server will send only from and to addresses within our domain. (Or so it seems.)

Anyway, now it works. Here's the complete code as constructed entirely with IntelliSense and some help from the compiler squiggles:
Dim fromAddress As New MailAddress("mike@work.com", "Mike @ work")
Dim toAddress As New MailAddress("mike@work.com", "Mike @ work")
Dim msg As New MailMessage(fromAddress, toAddress)
msg.Body = "<b>Testing new email</b>"
msg.Subject = "Testing new email, sent at " & DateTime.Now.ToString()
msg.IsBodyHtml = True

Dim mailSender As New System.Net.Mail.SmtpClient()
mailSender.Host = "(hostname)"
Try
mailSender.Send(msg)
Label1.Text = "Message sent."
Catch ex As Exception
Label1.Text = ex.Message
End Try
[1] Actual email address changed to protect the ... well, to protect me.




Alan Wright   13 Sep 05 - 2:42 PM

You can also specify the mailsender host and port like this

Dim smtp As New SmtpClient("Mail.yahoo.com", 21)



 
sgrayson   09 Dec 05 - 6:19 PM

Now the question is, did the documentation provide a better explanation or example than just using Intellisense?

 
mike   10 Dec 05 - 1:25 PM

Excellent question. I don't remember all that well now, but I can tell you that the method for adding embedded graphics was not documented in the docs -- I ended up getting that off an internal email query.

In general, IntelliSense helped get me started, assuming a wee bit of prior knowledge on my part. But I would eventually have needed to turn to the docs. Whether the docs actually close the gap depends entirely on what a person is looking for and whether the docs in question happen to address that issue. It's by no means a sure thing, but then again, often the examples in the docs, along with the odd but critical sentence in the Remarks, do the trick exactly.


 
gmstar   09 Feb 06 - 2:18 PM

I do this like you ,but I have same error.Why?
My computer is one of the domain.


 
mike   09 Feb 06 - 2:54 PM

gmstar, you might try Dave Wanta's site:

http://systemnetmail.com/

or the ASP.NET forums (http://forums.asp.net/)