Sending Email Messages

Problem: Every morning, you want to count the number of requests in the IIS log made over the past 24 hours and send your boss an HTML-formatted report via e-mail.

This time, there are two problems to solve. The first involves reading the IIS log, and the second involves sending e-mail. I'll deal with them in order because the first problem is much more involved than the second—sending e-mail in .NET is simple. Understanding and reading the IIS log is vital for tracking your site's usage, finding security attacks and breaches, and generally understanding what's happening with your Web server, so although the task isn't exactly aligned with the goal of this chapter—messaging—it is pertinent to the book topic itself.

Before starting on the problem, you should recognize that this solution doesn't have to be implemented in a Web application—it could just as easily be a standard application or a service. But as you work through the rest of this book, you're likely to realize that that's true of almost every C# Web application— the main reason to write the user interface as a Web-based application is that you don't have to install code on the client. At the same time, .NET has made installing code on the client much easier, so even that's not as good a reason as it used to be.

Reading the IIS Log File

You've already seen how to write to the IIS log using the Response.AppendToLog method. By default, IIS creates a new log file every day in the WinNT\System32\Logfiles\W3svc folder. IIS also logs every request by default; in fact, you may be surprised by how much information the server maintains for each request.

The log file is a comma-delimited text file; each record is a single line. You can read it just like any other text file. Because the log file location is configurable, it's possible that your IIS server logs requests somewhere else. You can check easily. Start the Internet Information Services management application (Start ® Programs ® Administrative Tools ® Internet Services Manager). Right-click your default Web site and select Properties. You'll see the Default Web Site Properties dialog. Click the Web Site tab. The bottom panel on that dialog shows the overall log settings for your server (see Figure 11.4).

I HTTPH±Klirt ] LulT-* i-WOHi | ^tl^ii £>JLW!F>t | Vjfrilr | Uf^iV.n] | PbfinwM | liiflfHai \ HwisEr| Docii-iH: |

WthitiMtftUaa

DemftTi

Inn** Wife 5n

IPAAJwi

_*J Jatw,d |

TCP Pai

|S)

if uriT' rtpAhfsEr^tJW I? Fr.iMtiomrfl

IWX.EfTvkdLqFitFOTvt 3 I

Figure 11.4: The IIS Default Web Site Properties dialog

Check the Enable Logging check box if it is not already selected. The rest of this section won't make sense if IIS logging isn't enabled.

On my server, Active Log Format is set to the default, W3C Extended Log File Format; however, the gist of this section applies to any format except ODBC logging, which logs to a database table, not a file. The default format lets you aggregate log data between IIS and any other Web server that supports the standard W3C Extended Log format. The NCSA format and the Microsoft IIS Log format are fixed-field formats; you can't customize the data that IIS logs. In contrast, the delimited W3C format lets you choose the fields you want. IIS inserts the comma delimiters for the fields that you don't need, which facilitates data merges with logs from other servers, but doesn't log the data. Limiting the fields you log can marginally reduce the load on a busy server. Turning off logging altogether, of course, has a much greater effect than limiting the number of fields.

If you have the ODBC logging option set, your server logs data to a database rather than to a file. It's more resource intensive to log to a database, but you can use the database to sort and filter the data for reports. Because of the overhead, I recommend that you log to a file. You can import the data into a database periodically (during off-peak hours) to provide exactly the same report capabilities.

It's interesting to look at the fields that IIS saves for each request. Click the Properties button to view the Extended Logging Properties dialog. The dialog you see depends on which log format you have selected. For the W3C Extended Log format, you'll see the dialog in Figure 11.5.

Lwtended logging Profwrtie»

a«**Jl^^flU: | EUiroriPllpfitrj I NevLogTfinePtfttd r Hikjv " Da*

r wcru,

r~ ^btkxiJ^i^h ii .-rj Andrafaci

U.J rt. lUStlf.;,.

IVJ/ri4 f.Vjj j^.jiUiijfif. i

Figure 11.5: The extended log format dialog for the W3C Extended Log format

The set of options in the New Log Time Period portion of the dialog controls how often IIS creates a new log file. By default, IIS keeps one log per day (the Daily setting). For high-use sites, you might want to use the Hourly setting so that the log files remain small enough to process. The Log File Directory field in the bottom panel of the dialog shows the path that IIS will use to create log files. The %WinDir% path variable substitutes for the root Windows path on your machine. On most computers, the path resolves to C:\WINNT.

The settings you choose affect the frequency of log "rollover"—when IIS creates a new log and the name of the log files. The Use Local Time for File Naming and Rollover check box controls whether IIS uses local time or Greenwich Mean Time (GMT) to control when the log file changes. You need to know settings for New Log Time Period, Use Local Time for File Naming and Rollover, and Log File Directory to work through this example.

Note Write down the settings on this page, because you'll need them shortly. The example uses the default settings: daily log rollover, GMT file naming, and the %WinDir%\System32 \LogFiles path.

Next, click the Extended Properties tab (see Figure 11.6).

LHtended lodging Properties tmoaifi^wiifj Crfi^JKiPicfmtij |

) l.if I n-el

E

I ■ tcnrirr ■

. 'fivti HYM I. : t

</. £«1*44 lipal I

f Mtll*^ 1 CS<TK*H!l I

V UK l¥TT 1 ¿LUILWinl

•s :.M':IQ'j^v !c: J-QJC:. 1

S Fmocd 5<Hin 1 Pill*«; 1

. U'r-SJiltfj? | mniMUiii 1

Figure 11.6: IIS Extended Logging Properties dialog for the W3C Extended Log format

You can select any of the fields. The active fields are those with the check box selected. In addition, several fields save server performance data for each request, among which are Total User Time and Total Kernel Time. Refer to the Help file (click the Help button on the dialog) if you're not familiar with the field names or meanings.

You read the log in the same manner that you read any other sequential file. However, because log files can be extremely large, you probably should not read the entire file into memory. Instead, read the file one line at a time, keeping a running total for the report.

Before you can read the log, you have to find the current log. Fortunately, IIS uses a naming convention of "ex" plus the current date in some form—YYMMDD by default. Unfortunately, it's not quite that easy because the name depends on the log rollover frequency setting and the setting of the Use Local Time... check box. For example, if your server creates a new log daily using local time, you can find the name by formatting the current date in yymmdd format and concatenating that with "ex". However, if your server creates a new log hourly, you must format the current date and time in yymmddhh format. If weekly, the server uses a yymm0n format, where n is the week number in the current month. (I haven't explored beyond that because most people would not set log rollover frequency to Monthly or Yearly.)

If your server uses GMT rather than local time, you must obtain the GMT date or date/time value and format that. At any rate, you must know how your server is set up to create a valid log filename. The Web Form ch11-3.aspx shows how to retrieve the log filename when the rollover frequency is set to Daily.

Note You may need to change the date formatting in the example if your server log rollover frequency is not set to Daily.

Import the System.IO and System.Globalization namespaces:

using System.IO;

using System.Globalization;

Create an enum that specifies the log rollover frequency, and one for the IIS time setting, which may be

GMT or Local.

private enum IISLogFrequency { Hourly = o, Daily = 1

private enum IISTimeSetting { GMT = 0, Local = 1

The following code retrieves the log filename, given the log frequency and the time setting.

private String getLogFileName(IISLogFrequency logFrequency , IISTimeSetting timeSetting) { DateTime aDate = DateTime.Now; String sDate=""; String logFileName; String sysFolder;

sysFolder = System.Environment.GetFolderPath

(Environment.SpecialFolder.System); switch (timeSetting) {

case IISTimeSetting.GMT :

aDate = DateTime.Now.ToUniversalTime(); break;

case IISTimeSetting.Local : aDate = DateTime.Now; break;

switch (logFrequency) {

case IISLogFrequency.Daily :

sDate = aDate.ToString("yyMMdd"); break;

case IISLogFrequency.Hourly :

sDate = aDate.ToString("yyMMddhh"); break;

logFileName = sysFolder + "\\logfiles\\w3svc1\\ex" + sDate + ".log";

if (File.Exists(logFileName)) { return (logFileName);

return null;

The method accepts an IISLogFrequency enumeration value (either Hourly or Daily) and an IISTimeSetting enumeration value (either GMT or Local). There are a few interesting points. First, you don't need to know where the system folder is on your server—the .NET framework will tell you. Use the System.Environment.GetFolderPath method. The method accepts an instance of the

System.Environment.SpecialFolder enumeration, in this case SpecialFolder.System, and returns the associated path:

sysFolder = System.Environment.GetFolderPath (Environment.SpecialFolder.System);

Second, you can obtain the GMT time, also called Universal Time or UTC, using the ToUniversal-Time method:

aDate = DateTime.Now.ToUniversalTime();

Finally, you format dates by calling ToString with a DateTimeFormatinfo property value or format string. The format strings are similar to, but not the same as, classic VB's Format function. You can

create custom formats by combining the format patterns (see Tables 11.1 and 11.2). Table 11.1: Standard Date/Time Formats (from the MSDN .NET Documentation)

Format Character

Format Pattern

Associated Property/Description

d

MM/dd/yyyy

ShortDatePattern

D

dddd, dd MMMM yyyy

LongDatePattern

f

dddd, dd MMMM yyyy HH:mm

Full date and time (long date and short time)

F

dddd, dd MMMM yyyy HH:mm:ss

FullDateTimePattern (long date and long time)

g

MM/dd/yyyy HH:mm

General (short date and short time)

G

MM/dd/yyyy HH:mm:ss

General (short date and long time)

m, M

MMMM dd

MonthDayPattern

r, R

ddd, dd MMM yyyy HH':'mm':'ss 'GMT'

RFC1123Pattern

s

yyyy'-'MM'-'dd'T'HH':'mm':'ss

SortableDateTimePattern (based on ISO 8601) using local time

t

HH:mm

ShortTimePattern

T

HH:mm:ss

LongTimePattern

u

yyyy'-'MM'-'dd HH':'mm':'ss'Z'

UniversalSortableDateTimePattern

(based on ISO 8601) using Universal Time

U

dddd, dd MMMM yyyy HH:mm:ss

Sortable date and time (long date and long time) using Universal Time

y, y

yyyy MMMM

YearMonthPattern

Table 11.2: DateTime Custom Formatting Patterns (from the MSDN .NET Documentation)

Format Pattern

Description

d

The day of the month. Single-digit days will not have a leading zero.

dd

The day of the month. Single-digit days will have a leading zero.

ddd

The abbreviated name of the day of the week, as defined in

AbbreviatedDayNames.

dddd

The full name of the day of the week, as defined in DayNames.

M

The numeric month. Single-digit months will not have a leading zero.

MM

The numeric month. Single-digit months will have a leading zero.

MMM

The abbreviated name of the month, as defined in

AbbreviatedMonthNames.

MMMM

The full name of the month, as defined in MonthNames.

y

The year without the century. If the year without the century is less than 10, the year is displayed with no leading zero.

yy

The year without the century. If the year without the century is less than 10, the year is displayed with a leading zero.

yyyy

The year in four digits, including the century.

gg

The period or era. This pattern is ignored if the date to be formatted does not have an associated period or era string.

h

The hour in a 12-hour clock. Single-digit hours will not have a leading zero.

hh, hh*

The hour in a 12-hour clock. Single-digit hours will have a leading zero.

H

The hour in a 24-hour clock. Single-digit hours will not have a leading zero.

HH, HH*

The hour in a 24-hour clock. Single-digit hours will have a leading zero.

m

The minute. Single-digit minutes will not have a leading zero.

mm, mm*

The minute. Single-digit minutes will have a leading zero.

s

The second. Single-digit seconds will not have a leading zero.

ss, ss*

The second. Single-digit seconds will have a leading zero.

t

. The first character in the AM/PM designator defined in AMDesignator or PMDesignator.

tt, tt*

The AM/PM designator defined in AMDesignator or PMDesignator.

z

The time zone offset (hour only). Single-digit hours will not have a leading zero.

zz

The time zone offset (hour only). Single-digit hours will have a leading zero.

zzz, zzz*

The full time zone offset (hour and minutes). Single-digit hours and minutes will have leading zeros.

The default time separator defined in TimeSeparator.

/

The default date separator defined in DateSeparator.

% c

Where c is a format pattern if used alone. The % character can be omitted if the format pattern is combined with literal characters or other format patterns.

\ c

Where c is any character. Displays the character literally.

After you obtain the log filename, counting the log entries is easy. Open a StreamReader on the file and loop through it, counting the lines.

private int countLogFileEntries(String logfilename) { StreamReader sr = null; FileInfo fi; int lineCount=0; String s;

fi = new FileInfo(logfilename); try {

sr = new StreamReader(fi.Open(FileMode.Open,

FileAccess.Read, FileShare.ReadWrite)); do {

if (s.Substring(0, 1) != "#") { lineCount += 1;

catch (Exception ex) {

Response.Write(ex.Message);

finally {

return (lineCount);

As you loop through the lines in the file, skip the lines that begin with a number sign (#), because they aren't valid log entries. Press F5 a few times to refresh the browser. Note what happens to the count. It increases by two (or three), rather than one, each time you refresh the page. That's because the Web Form contains a reference to a style sheet. Using IE5.5, the page makes two calls to the server—one for the page and one for the style sheet. Using IE6, the page makes three calls to the server—one for the page and two for the style sheet. I suspect the difference is that IE6 is still in beta as of this writing. Because only one style sheet reference is in the page, by rights the server ought to receive only one request for it. In any case, if you look at the protocol status, you'll see that for all but the initial request by any given client, the status value is 304—which means "Not Modified," the client has an up-to-date cached copy—so it takes very little time. Logging the request takes more time than returning the status.

The Web Form ch11-3.aspx displays the line count. Automating an E-mail Message

Now that you can see the information, you can automate delivering it to your boss via e-mail. As I said at the beginning of this discussion, that is the easy part. The simplest way to send an e-mail message is to call the static Send method of the SmtpMail class with four parameters: the source account; the destination account; a subject, which will appear on the Subject line in the destination account's mail program; and the message body. For example:

System.Web.Mail.SmtpMail.Send ( "[email protected]", "[email protected]", "This is the subject.", "This is the message.");

This version of the Send method sends a text-formatted message. The Send method has an overloaded version that accepts an instance of a MailMessage class. To send HTML-formatted mail, you must use this second version. But it isn't much more difficult. Create a MailMessage object and set its properties, then send it via the overloaded Send method. Here's a function that sends a MailMessage object using the Send method:

private void sendMail(String fromAccount, String toAccount, String title, String msg,

System.Web.Mail.MailAttachment attachment)

System.Web.Mail.MailMessage mm = new

System.Web.Mail.MailMessage();

mm.To = toAccount;

mm.From = fromAccount;

mm.Subject = title;

mm.BodyFormat = System.Web.Mail.MailFormat.Html; if (attachment != null) {

mm.Attachments.Add(attachment);

System.Web.Mail.SmtpMail.Send(mm);

The code is very straightforward. You'll probably notice immediately that the function handles an attachment as well. To send an attachment, you create a MailAttachment object. You can pass the constructor a filename. The Web Form ch11-3 has two Text controls: an HTML File Field control (see Chapter 10 for information on setting up a Web Form to use the File Field control) and a button (see Figure 11.7).

EggBaannae^m m twm r4srt.11 r^H: 1 tls.

UV0111C1 In s

MUmthfim!_

I EWl EPWJ I

Figure 11.7: Form to send an email message

Note To run the Web Form chll-3 successfully, you must have an SMTP server installed on your Web server (installing IIS installs an SMTP server by default).

When you run the chll-3 Web Form, it displays the path to the log file used to generate the report and the number of entries in the log and lets you enter a From e-mail address and a To e-mail address. You may also attach a file. When you click the Send E-mail button, the page sends an e-mail message. If you specify a file to attach by selecting or entering a valid filename in the Attach File File Field control, the server sends that file as an attachment. First, it creates a temporary filename, using the Path.getTempPath static class method. Next, it strips the filename from the uploaded file and uses the PostedFile.SaveAs method to save the file. It then sends the e-mail message and deletes the temporary file. Here's the code:

private void Buttonl Click(object sender, System.EventArgs e) { String toAccount, fromAccount, title, msg, attachFile; System.Web.Mail.MailAttachment attachment=null; String tmpFileName=""; title = "Hits today!";

msg = "<html><head></head><body style=\"background:" +

"lightyellow; color:blue; font-family: Verdana, Arial; " + "font-size: 9pt;\">" +

"<div border=\"l\">We had " + logFileCount.ToString() + " hits today.</div></body></html>"; fromAccount = txtFrom.Text; toAccount = txtTo.Text;

attachFile = attachments.PostedFile.FileName; if (attachFile != "") {

attachFile = attachFile.Substring(

(int) attachFile.LastIndexOf("\\") + l); tmpFileName = Path.GetTempPath() + attachFile; attachments.PostedFile.SaveAs(tmpFileName);

attachment = new System.Web.Mail.MailAttachment(tmpFileName);

attachFile = null;

sendMail(fromAccount, toAccount, title, msg, attachment); if (tmpFileName !=null) {

File.Delete(tmpFileName);

So now you know how to read the IIS log (and may have learned something about the .NET framework's date handling capabilities as well) and how to send e-mail messages. Spend a little time looking at all the MailMessage class methods and properties, because I didn't discuss some properties in this section.

Was this article helpful?

0 0

Post a comment