Tom Moran
Microsoft Corporation
October 1, 1998
The following article was originally published in the Site Builder Network Magazine "Servin' It Up" column (now MSDN Online Voices "Servin' It Up" column).
Contents
How do I send mail from an ASP page?
What is this 80004005 error message, and what can I do about it?
How do I give everyone in my company authenticated access to specific Web pages?
Can I use session object variables with multiple servers?
How do I conditionally include files (AKA, virtual include)?
Why does the order of ASP execution seem to be so random?
Tom's tips: ASP performance
Tom: There are a several ways to do this. The best right now is to use CDO (Collaboration Data Objects) for NTS, which is basically a lightweight version of the CDO for Exchange. In the distant past, with Internet Information Server 3.0, some people used the Microsoft Personalization System SendMail component from Site Server 2.0 to send mail. However, with CDO is built into IIS 4.0, so you get it for free with the Windows NT Option Pack. You can also do things like send MIME encoded messages, which was unavailable with the older SendMail object. For all intents and purposes, think of CDO for NTS as replacing the SendMail object.
At a minimal level, you can send mail from an ASP page using CDO in about four lines:
<% Dim myCDO Set myCDO = Server.CreateObject("CDONTS.NewMail") myCDO.Send "From@importantcustomer.com", "servin@microsoft.com","Servin It Up", "Love your column. The Web Men are cool, too." Set myCDO = nothing %>
Just replace your e-mail address in the "from@importantcustomer.com" section and run the above code. I set the myCDO object to nothing at the end, since it can only be used once, and needs to be recreated every time a message is sent.
You might want to use IF IsObject(myCDO) = FALSE to determine if the object was created correctly, as well as do any other error checking that might be important to you. You can also set each parameter individually, instead of passing them all together as I did. Parameters available are the usual e-mail parameters of to:, importance, subject, file attachments, body, etc.
If this doesn't work upon first try, you probably need to go into the Microsoft Management Console (MMC) and ensure that you have correctly installed the SMTP (Simple Mail Transfer Protocol) service. The SMTP service is installed by default for IIS 4.0, so you should at least have it installed. And contrary to popular opinion, you do not need to have Exchange installed. In fact, as long as the SMTP Service is running on your server, it can point to any valid SMTP server.
If there is enough interest, I'll put together a complete sample application that shows some variations on how to work with the CDO and post it here next month. There are a lot of things you can do, including mailing from forms, including HTML as a body, and attaching files.
Resources:
Although I've never used them, there are two commercially available products that may make it easier to send mail. Here are the links to Server Objects Inc.:
Tom: This is the most common error you'll see. It is a general error telling you the data you want cannot be accessed for some reason. Usually you'll get a moderately helpful explanation along with the 80004005 notification. In most cases, it comes down to lack of permission or DSN naming problems. However, note that you can get this for other reasons as well, including the wrong type of parameter sent to a SQL Stored Procedure. The error is actually bubbled up from the data provider, so sometimes it can be hard to tell exactly what happened or where.
To troubleshoot, you first need to check your permissions. The Microsoft Knowledge Base article Q175671 has some good trouble-shooting tips. Make sure that your account has full permission on the directory that contains the data and files. This means that, if you are using anonymous access, the IUSR_<machine name> account needs to have that access. You can also temporarily add the IUSR account to the Administrators.
If everything works now, you know there is some sort of account-permissions problem and can start troubleshooting. If things don't work, that wasn't the problem, so don't forget to set the permissions back to the way they were, especially if you added IUSR to the Administrator group. Now, this could still be a permissions issue. Are the data source and IIS on the same machine? If not, permission credentials can get lost in the whole delegation routine and cause problems. If you are relying on authentication, you may need to change the default network protocol from what is probably Named Pipes to be TCP/IP Sockets (Transmission Control Protocol/Internet Protocol), which you can do through the SQL Server Client Configuration Utility. The problem is that IIS tries to impersonate a user, but fails and is denied the connection. Next most common issue is with the DSN. Just make sure that you haven't moved your data source from a test machine to somewhere else without changing the data names. This is the most common related problem.
There are many causes of the 80004005 error, but they all have to do with some sort of problem getting to your data, and so are generally fairly easy to track down. Start with permissions, check your DSN, and then start cruising the Microsoft Knowledge Base and you'll get there.
Resources:
Since each 80004005 that comes up may have a very specific set of circumstances, Microsoft Knowledge Base articles are your best source of information. If you select All Products, you end up with various articles about Microsoft PowerPoint getting the error, and that's probably not what you want. But if you just select ASP or ADO or Visual InterDev, you don't get the complete picture.
So I've done all the work of stripping out the most inappropriate articles. As long as you are registered with Support Online, you should be able to click to find the best articles on Support Online .
The best article of the bunch is Q183060 .
Tom: Assuming you are on a Windows NT network, just give the Windows NT group "Everyone" access to the desired file or directory. Make sure, though, that you follow that up by disabling the IUSR_<machine name>account for that directory or file. If you never have need for anonymous access, then simply remove that option from IIS for that Web completely.
Tom: See the new MSDN Online Web Workshop Workshop article Using the Membership Directory and ActiveX User Object for Session State Data. The basic problem is that the session is specific to the server that processes the request. Site Server Personalization and Membership Services, however, were designed to provide this capability, and have several other benefits, such as potentially increased performance, easy access to all user data, and so forth.
Tom: For those of you who haven't tried this, the idea is to be able to substitute the appropriate include file without knowing beforehand which file to include. For example, if a person from the accounting team signs on, you want to include accounting.inc. But if a person from the payroll department signs on, you want to include payroll.inc. This is more difficult than it sounds. Even a new programmer would quickly come up with the idea of creating a variable, filling it with the appropriate value, and referencing the variable inside the include statement.
<% name=(header1 & ".inc")%> <!--#include file="<%= name %>"-->
Regrettably, building a filename to include doesn't work like that. Your ASP page is compiled and executed before being sent to the client, which means that your variable isn't evaluated until after the include statement has been evaluated. There are three common ways and one difficult way to work around this. The first is to use the FileSystemObject, the second is to use a select case statement, the third is to use an if/then, and the difficult fourth is to write your own ISAPI DLL, which I won't go into here. An ISAPI filter can process a file ahead of ASP, intercepting files first to do custom processing before it then passes the file on. Perhaps someone knows of one already written that is available on a Web site somewhere. Let me know and I'll post a pointer to it.
To find as much information as possible about this question, I did the following:
Using the FileSystemObject (from an upcoming KB article by Shawn Vita):
<% ' The name of the file to display was passed by a form using GET method infil = Request.QueryString("file") If infil <> "" then set fso = Server.CreateObject("Scripting.FileSystemObject") set fil = fso.OpenTextFile(infil) filout = fil.ReadAll ' PRE tags preserve the format of the file Response.write "<PRE>" & filout & "</PRE><BR>" End If %>
Unfortunately, files must contain straight text/HTML. Due to the timing, files containing script will not work.
Using if/then (from a post by Aaron Bertrand in the ActiveServerPages Newsgroup ):
<% filename=request.querystring("Filename") if filename="blah.txt" then %> <!--#include file="blah.txt"--> <% elseif filename="blah2.txt" %> <!--#include file="blah2.txt"--> <% end if %>
Of course, each line is processed; it's just that the user will not see anything except the desired result.
Using the select case statement (from a post on the ASP listserv by Chris Woodrow):
<% select case includename %> <% case "file1" %> <!--include..... file1 --> <% case "file2" %> <!--include..... file2 --> <% end select %>
This is somewhat similar to the previous solution. Each file is loaded into memory, so this is not the most efficient way to do things, but it does get the job done.
Tom:To illustrate this, look at the following two ASP files and the corresponding output:
<%@ Language=Javascript %> <HTML> <HEAD> <META NAME="GENERATOR" Content="Microsoft Visual Studio 6.0"> </HEAD> <BODY> Block 1 - Straight HTML - Before the Code Block<P> <SCRIPT LANGUAGE=javascript RUNAT=Server> Response.Write ("Block 2 - SCRIPT LANGUAGE=javascript RUNAT=Server<P> "); </SCRIPT> <% Response.Write ("Block 3 - Javascript using the script delimiters<P>") %> <SCRIPT LANGUAGE=VBScript RUNAT=Server> Response.Write ("Block 4 - SCRIPT LANGUAGE=VBScript RUNAT=Server<P>") </SCRIPT> Block 5 - Straight HTML - After theCode Block<P> </BODY> </HTML>
Web Browser Output:
Block 4 - SCRIPT LANGUAGE=VBScript RUNAT=Server Block 1 - Straight HTML - Before the Code Block Block 3 - Javascript using the script delimiters Block 5 - Straight HTML - After theCode Block Block 2 - SCRIPT LANGUAGE=javascript RUNAT=Server
If the following code is run (Default script language has been changed to VBScript)
<%@ Language=VBScript %> <HTML> <HEAD> <META NAME="GENERATOR" Content="Microsoft Visual Studio 6.0"> </HEAD> <BODY> Block 1 - Straight HTML - Before the Code Block<P> <SCRIPT LANGUAGE=javascript RUNAT=Server> Response.Write ("Block 2 - SCRIPT LANGUAGE=javascript RUNAT=Server<P> "); </SCRIPT> <% Response.Write ("Block 3 - Javascript using the script delimiters<P>") %> <SCRIPT LANGUAGE=VBScript RUNAT=Server> Response.Write ("Block 4 - SCRIPT LANGUAGE=VBScript RUNAT=Server<P>") </SCRIPT> Block 5 - Straight HTML - After theCode Block<P> </BODY> </HTML>
Web Browser Output:
Block 2 - SCRIPT LANGUAGE=javascript RUNAT=Server Block 1 - Straight HTML - Before the Code Block Block 3 - Javascript using the script delimiters Block 5 - Straight HTML - After theCode Block Block 4 - SCRIPT LANGUAGE=VBScript RUNAT=Server
The thing to remember:there is no guaranteed execution order for code inside script blocks. Note that the language opposite to your default gets called, then comes the HTML and script delimiters in the order you expect, then the script block containing the default language. However, even figuring out how it seems to work is dangerous, since it could very well change in any system updates or product revs.
The recommendation is to only use procedures or functions inside script blocks, and call these functions from within your primary script, which gives control to you -- that is undoubtedly what you want. An example of the way the above code could have been written to get the expected output is:
<%@ Language=VBScript %> <HTML> <HEAD> <META NAME="GENERATOR" Content="Microsoft Visual Studio 6.0"> </HEAD> <BODY> <P> </P> Block 1 - Straight HTML - Before the Code Block<P> <SCRIPT LANGUAGE=javascript RUNAT=Server> function WriteBlock2() { Response.Write ("Block 2 - SCRIPT LANGUAGE=javascript RUNAT=Server<P> "); } </SCRIPT> <% WriteBlock2() Response.Write ("Block 3 - Javascript using the script delimiters<P>") WriteBlock4() %> <SCRIPT LANGUAGE=VBScript RUNAT=Server> Sub WriteBlock4() Response.Write ("Block 4 - SCRIPT LANGUAGE=VBScript RUNAT=Server<P>") End Sub </SCRIPT> Block 5 - Straight HTML - After theCode Block<P> </BODY> </HTML>
Thanks to Jeff Sandquist from Developer Support, who mentioned some of his customers were having problems with this. He also provided some of the code to illustrate the issue. I'll be sending him one of those highly coveted MSDN T-shirts.
The best tip I can give you in a short space for improving your performance -- other than to try Viagra -- is to make sure you are using Active Server Pages technology in the right places. Don't use ASP just so that all your pages will end with the same extension. ASP is cool, but because it requires work on the server side, it doesn't come free. If you have a fairly static page that isn't going to be customized, go ahead and leave it as HTML.
A good example might be a company presence on the Web. Often, the first page is the same for everyone and is changed by hand when merited. But the sub pages have things like shopping carts or news. Make this first page an HTML page, and, with high volume, you'll notice the difference. More importantly, your customers will notice. After all, you want visitors' first experiences to be very snappy, or they'll probably go somewhere else.
Tom Moran is a program manager with Microsoft Developer Support and spends a lot of time hanging out with the MSDN Online Web Workshop folks. Outside of work, he practices kenpo (although sometimes necessary at work), tries out original recipes on his family (Lisa, Aidan, and Sydney), leads white-water trips, or studies tax law (boring, but true).