Pages

Advertisement

Thursday, July 12, 2007

Sending Emails Through Outlook using C# and VB.NET

Introduction
In this article I will give you an example of how to add an e-mail to your Microsoft Outlook outbox folder using C# and/or VB.net. This example also show how easy it is to call functions written in VB.net from C#
The code consists of three classes: Form1.cs, CSharp.OutlookMail.cs, VBNET.OutlookMail.vb.
Form1: a simple Windows Forms which shows how easy it is to call a C# or VB.net function.
CSharp.OutlookMail.cs: C# class with one function to add an e-mail to outlook outbox.
VBNET.OutlookMail.cs: VB.net class with one function to add an e-mail to outlook outbox.
The first thing you need to do is to add a reference to "Microsoft Outlook 9.0 Object Library" Click on add Reference, select the COM tab and select "Microsoft Outlook 9.0 Object Library".

public class OutlookMail
{
private Outlook.Application oApp;
private Outlook._NameSpace oNameSpace;
private Outlook.MAPIFolder oOutboxFolder;
public OutlookMail()
{


//Return a reference to the MAPI layer


oApp = new Outlook.Application(); 


The Namespace object represents the messaging service provider. In order to get access to all Outlook folders and items we have to use the MAPI namespace.


oApp = new Outlook.Application();
oNameSpace= oApp.GetNamespace("MAPI");


Now that we have the MAPI namespace, we can log on using using:


<mapinamespace>.Logon(object Profile, object Password, object ShowDialog, object NewSession)


Profile: This is a string value that indicates what MAPI profile to use for logging on. Set this to null if using the currently logged on user, or set to an empty string ("") if you wish to use the default Outlook Profile.
Password: The password for the indicated profile. Set to null if using the currently logged on user, or set to an empty string ("") if you wish to use the default Outlook Profile password.
ShowDialog: Set to True to display the Outlook Profile dialog box.
NewSession: Set to True to start a new session or set to False to use the current session.
oNameSpace.Logon(null,null,true,true);
We now choose which folder we want to work with. A MAPIFolder object represents a single Outlook folder. For example you could use:


Calender: Outlook.OlDefaultFolders.olFolderCalendar
Contacts: Outlook.OlDefaultFolders.olFolderContacts
Inbox: Outlook.OlDefaultFolders.olFolderInbox
For this example we choose the Outbox folder
//gets defaultfolder for my Outlook Outbox
oOutboxFolder = oNameSpace.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderOutbox);
}


The following function takes 3 string as parameters. These will be the values that we will add to the to, subject and the email body fields. We create a MailItem, and set the To, Subject, and Body fields.


public void addToOutBox(string toValue, string subjectValue, string bodyValue)
{
Outlook._MailItem oMailItem = (Outlook._MailItem)oApp.CreateItem(Outlook.OlItemType.olMailItem);
oMailItem.To = toValue;
oMailItem.Subject = subjectValue;
oMailItem.Body = bodyValue;
oMailItem.SaveSentMessageFolder = oOutboxFolder;
//uncomment this to also save this in your draft
//oMailItem.Save();
//adds it to the outbox
oMailItem.Send();
}
}


Conclusion:
Microsoft .NET is extremely powerful and yet simple to work with. In this example, I showed how to add e-mail to Outlook outbox. In the next verion, I will add functions to add tasks, calender and contacts items.

Understanding System.Buffer Class

Before we try out something with the Buffer class, you should understand the Buffer class and its Members. The first question that comes to mind is how they are different from the Array class and how they work as compared to the Array class.

Introduction:

Before we try out something with the Buffer class, you should understand the Buffer class and its Members. The first question that comes to mind is how they are different from the Array class and how they work as compared to the Array class.

This article is basically to answer the above questions and to help you understand how the Buffer class works. The word Buffer implies that the class works on direct Memory. In .Net it's basically a manipulation of unmanaged memory represented as arrays of bytes. So lets take a example of a program where we will copy one array data into another using the Array class as the first example and then compare it with a Buffer class example.

In System.Array we have a Copy() Member to copy from one array to another. Let's take an example with an array of five elements: Myarr1[5]. This array will be initialized with data 1, 2, 3, 4, and 5 and another array of ten elements called Myarr2[10] with contain data 0, 0, 0, 0, 0, 6, 7, 8, 9, 10.

The length of an array is the same as the number of elements in the array. In our example Myarr1 has five elements so array length is 5. Myarr2 has ten elements so the array length is 10. The Array class has a Copy() method which copies the contents of one array into another. It can copy a range of elements from an array starting at the specified source index and pastes them to another array starting at the specified destination index. So, the Copy() method takes five parameters. They are source array, source index, destination array, destination index, and the number of elements to copy.

Note: I am taking array index starting from 1 for better understanding in examples

Before Array.Copy() operation:

Myarr1[1]  Myarr1[2]  Myarr1[3]  Myarr3[4]  Myarr4[5]
1(data) 2(data) 3(data) 4(data) 5(data)

Myarr2[1] Myarr2[2] Myarr2[3] Myarr2[4] Myarr2[5]
0(data) 0(data) 0(data) 0(data) 0(data)

Myarr2[6] Myarr2[7] Myarr2[8] Myarr2[9] Myarr2[10]
6(data) 7(data) 8(data) 9(data) 10(data)

After Array.Copy() operation:

Myarr1[1]  Myarr1[2]  Myarr1[3]  Myarr3[4]  Myarr4[5]
1(data) 2(data) 3(data) 4(data) 5(data)

Myarr2[1] Myarr2[2] Myarr2[3] Myarr2[4] Myarr2[5]
1(data) 2(data) 3(data) 4(data) 5(data)

Myarr2[6] Myarr2[7] Myarr2[8] Myarr2[9] Myarr2[10]
6(data) 7(data) 8(data) 9(data) 10(data)

Here is the program for using Array.Copy():

Example 1:

namespace ConsoleApplication1
{
class Array1
{
static void
Main(string[] args)
{
int[] myarr1 = new int[5] {1,2,3,4,5};
int[] myarr2 = new int[10] {0,0,0,0,0,6,7,8,9,10};

Console.Write("Before Array copy operation\n");

Console.Write("Myarr1 and Byte Length{0}\n",myarr1.Length);
foreach(int i in myarr1)
Console.Write("{0} \t",i);

Console.WriteLine("\nMyarr2 and Byte Length:{0} \n",myarr2.Length);
foreach(int i in myarr2)
Console.Write("{0} \t",i);


//here we are copying index to index as data
Array.Copy(myarr1,0,myarr2,0,5);

Console.Write("After Array copy operation\n");

Console.Write("Myarr1:\n");

foreach(int i in myarr1)
Console.Write("{0} \t",i);

Console.WriteLine("\nMyarr2: \n");
foreach(int i in myarr2)
Console.Write("{0} \t",i);

//just for wait
Console.ReadLine();
}

}
}

Output:

Before Array Copy operation:
Myarr1 and Byte Length :5
1 2 3 4 5
Myarr2 and Byte Length:10
0 0 0 0 0 6 7 8 9 10
After Array Copy operation
Myarr1 :
1 2 3 4 5
Myarr2:
1 2 3 4 5 6 7 8 9 10

Now we will see same thing can be done using the System.Buffer class using the BlockCopy() method. But its not an index to index copy in the Buffer class. It's from offset to offset. The Buffer class copies a specified number of bytes from a source array starting at a particular offset to a destination array starting at a particular offset. Since we have taken integer arrays as example and we know int occupies four bytes. So offset values will be addition of 4 bytes from starting offset value.

In the System.Buffer class we have the BlockCopy() Member to copy from one array to another. Let's take an example with a array of five elements caled Myarr1[5]. This array is initialized with data 1, 2, 3, 4, 5. Another array of 10 elements called Myarr2[10] will be initialized with with data 0, 0, 0, 0, 0, 6, 7, 8, 9, 10.

In Buffer class the length means number of byte length in array. In our example Myarr1 has five elements so byte length is 5 (elements) x 4 (bytes of int)= 20 bytes. Myarr2 has ten elements so the byte length is 10 (elements) x 4 (bytes of int) = 40 bytes.

Buffer.ByteLength(Myarr1).

The Buffer class has the BlockCopy() method which copies one array content into another. It Copies a range of elements from an Array starting at the specified source start offset value and copies them to another Array starting at the specified destination offset value. So the BlockCopy() method takes five parameters. They are source array, source offset value, destination array, destination offset value, and the number of bytes to copy ( in our example we need to copy 5 elements then 5 x 4 = 20 bytes is the number of bytes to copy).

Before Buffer.BulkCopy() operation:

  Myarr1[1]     Myarr1[2]     Myarr1[3]      Myarr1[4]       Myarr1[5]
1 to 4 bytes 4 to 8 bytes 8 to 12 bytes 12 to 16 bytes 16 to 20 bytes
1(data) 2(data) 3(data) 4(data) 5(data)


Myarr2[1] Myarr2[2] Myarr2[3] Myarr2[4] Myarr2[5]
1-4 bytes 4-8 bytes 8-12 bytes 12-16 bytes 16-20 bytes
0(data) 0(data) 0(data) 0(data) 0(data)

Myarr2[6] Myarr2[7] Myarr2[8] Myarr2[9] Myarr2[10]
20-24bytes 24-28bytes 28-32bytes 32-26bytes 36-40bytes
6(data) 7(data) 8(data) 9(data) 10(data)

After Buffer.BulkCopy() operation:

  Myarr1[1]     Myarr1[2]     Myarr1[3]      Myarr1[4]       Myarr1[5]
1 to 4 bytes 4 to 8 bytes 8 to 12 bytes 12 to 16 bytes 16 to 20 bytes
1(data) 2(data) 3(data) 4(data) 5(data)

Myarr2[1] Myarr2[2] Myarr2[3] Myarr2[4] Myarr2[5]
1-4 bytes 4-8 bytes 8-12 bytes 12-16 bytes 16-20 bytes
1(data) 2(data) 3(data) 4(data) 5(data)

Myarr2[6] Myarr2[7] Myarr2[8] Myarr2[9] Myarr2[10]
20-24bytes 24-28bytes 28-32bytes 32-26bytes 36-40bytes
6(data) 7(data) 8(data) 9(data) 10(data)

Here is the program for using Buffer.BlockCopy():

Example 2:

using System;
namespace ConsoleApplication1
{
class buffer1
{
static void Main(string[] args)
{
int[] myarr1 = new int[5] {1,2,3,4,5};
int[] myarr2=new int[10] {0,0,0,0,0,6,7,8,9,10};

Console.Write("Before Block copy operation\n");
Console.Write("Myarr1 and Byte Length :{0}\n",
Buffer.ByteLength(myarr1));

foreach(int i in myarr1)
Console.Write("{0} \t",i);

Console.WriteLine("\nMyarr2 and Byte Length:{0} \n",
Buffer.ByteLength(myarr2));

foreach(int i in myarr2)
Console.Write("{0} \t",i);

//here we are copying offset to offet as bytes
Buffer.BlockCopy(myarr1,0,myarr2,0,20);

Console.Write("After Block copy operation\n");

Console.Write("Myarr1 :\n");
foreach(int i in myarr1)
Console.Write("{0} \t",i);

Console.WriteLine("\nMyarr2: \n");
foreach(int i in myarr2)
Console.Write("{0} \t",i);

//just for wait
Console.ReadLine();
}
}
}

Output:

Before Block Copy operation

Myarr1 and Byte Length :20
1 2 3 4 5

Myarr2 and Byte Length:40
0 0 0 0 0 6 7 8 9 10

After Block Copy operation

Myarr1 :
1 2 3 4 5

Myarr2:
1 2 3 4 5 6 7 8 9 10

If you observe both examples, you'll see that they give the same result but the way they operate is completely different. In the first example, you use Array.Copy(myarr1,0,myarr2,0,5);--> here 5 means the number array elements to copy. In example 2 you used Buffer.BlockCopy(myarr1,0,myarr2,0,20);--> here 20 means the number of bytes to copy. Five elements of type int occupy 20 bytes.

Downloads
Download source - 1 Kb

Using the Environment Class


Click here for a larger image.

Environment: .NET, C#

The .NET Framework takes a much different approach to the Environment than Windows. There is a class called Environment, part of the System namespace. The documentation states:

Use this class to retrieve the following information:

  • Command line arguments
  • Exit codes
  • Environment variable settings
  • Contents of the call stack
  • Time since last system boot
  • Version of the common language runtime

Besides getting all or a particular environment variable(s), there are other objects available. Most objects are read only, and there is no method to set or add a variable. As far as I can tell, only CurrentDirectory and ExitCode can be changed. There is no method to add or modify the environment variables. The most interesting thing I noticed when coding the example C# program was the way GetEnvironmentVariables() worked. The method returns an IDictionary object that represents a collection of key-and-value pairs. Another interesting method is GetFolderPath, which retrieves paths to various system folders.

//-----------------------------------------------------------

The following is a C# example of how to retrieve all environment variables.

case 0:
IDictionary id;
String sString;

ArrayList values = new ArrayList();

m_env_list.Items.Clear ();
id = Environment.GetEnvironmentVariables();
foreach (DictionaryEntry myEntry in id)
{
sString = myEntry.Key.ToString();
sString += "=";
sString += myEntry.Value.ToString();
m_env_list.Items.Add(sString);
}
m_env_list.SetSelected(0, true);
m_env_list_SelectedIndexChanged(this,null);
break;
//-----------------------------------------------------------

Downloads
Download source - 24 Kb

Getting Information About A File

This example presents the FileInfo class, which allows you to get information about a file. Listing 1 presents the FileInfo class in use. This program takes a single filename and displays the size and key dates regarding it. For the output, the FileSize.cs file was used.

Listing 1. FileSize.cs — Using the FileInfo Class

 1: //  FileSize.cs -
2: //-----------------------------------------------
3: using System;
4: using System.IO;
5:
6: class FileSize
7: {
8: public static void Main()
9: {
10: string[] CLA = Environment.GetCommandLineArgs();
11:
12: FileInfo fiExe = new FileInfo(CLA[0]);
13:
14: if ( CLA.Length < 2 )
15: {
16: Console.WriteLine("Format: {0} filename", fiExe.Name);
17: }
18: else
19: {
20: try
21: {
22: FileInfo fiFile = new FileInfo(CLA[1]);
23:
24: if(fiFile.Exists)
25: {
26: Console.WriteLine("===================================");
27: Console.WriteLine("{0} - {1}",
fiFile.Name, fiFile.Length );
28: Console.WriteLine("===================================");
29: Console.WriteLine("Last Access: {0}",
fiFile.LastAccessTime);
30: Console.WriteLine("Last Write: {0}",
fiFile.LastWriteTime);
31: Console.WriteLine("Creation: {0}",
fiFile.CreationTime);
32: Console.WriteLine("===================================");
33: }
34: else
35: {
36: Console.WriteLine("{0} doesn't exist!", fiFile.Name);
37: }
38: }
39:
40: catch (System.IO.FileNotFoundException)
41: {
42: Console.WriteLine("\n{0} does not exist!", CLA[1]);
43: return;
44: }
45: catch (Exception e)
46: {
47: Console.WriteLine("\nException thrown trying to copy file.");
48: Console.WriteLine(e);
49: return;
50: }
51: }
52: }
53: }



The following is the output from this listing. Your output will vary depending on when you create the FileSize.cs program.

===================================
FileSize.cs - 1551
===================================
Last Access: 2/22/2003 11:40:30 PM
Last Write: 2/22/2003 11:40:19 PM
Creation: 2/22/2003 11:39:45 PM

The FileInfo class creates an object that is associated to a specific file. In line 12, a FileInfo object was created called fiExe that is associated to the program being executed (fileinfo.exe). If the user doesn't enter an argument on the command line, the value of fiExe is printed with program usage information (line 14).

In line 22, a second FileInfo object is created using the argument passed to the program. In lines 26 to 32, information is displayed about this file. As you can see, the information displayed includes the file size using the Length member, the creation date and time using the CreationTime member, the last access date and time using the LastAccessTime member, and finally the last date and time the file was written to using the LastWriteTime member.

For more information

For more information check out my C# book, Sams Teach Yourself C# in 21 Days.

Sample Chapter: The .NET Base Class Libraries

Microsoft Visual C++ .NET 2003 Kick Start
Chapter 3: The .NET Base Class Libraries.
By Kate Gregory for Sams Publishing

In This Chapter

Libraries Shared Across Languages

When you write managed C++, you have access to all of the managed code libraries that come with the .NET Framework: the Base Class Libraries, ADO.NET, ASP.NET, and so on. These libraries are modern and powerful. They provide services, such as XML processing, that weren't even thought of when older libraries such as MFC and ATL were first written.

In sharp contrast to the historical capabilities of C++ and Visual Basic, on the .NET Framework these two languages share the same class libraries. Every library class and method that's available from Visual Basic is available from managed C++ and from C#. Every library class and method that's available from C# is available from Visual Basic and Managed C++.

The advantages of a shared class library are many. They include

  • Reduced learning time when moving from one .NET-supported language to another

  • A larger body of samples and documentation, because these do not need to be language specific

  • Better communication between programmers who work in different .NET-supported languages


The C++ Advantage - C++ can use the same class libraries as C# and VB.NET. Does it work the other way around? No. There are libraries of unmanaged code available from managed C++ that cannot be called from Visual Basic or C#—ATL and MFC are just two examples. However, it's unlikely that a Visual Basic or C# programmer would want to use those libraries, because their functionality is provided elsewhere; the capability to call them from managed C++ helps simplify a port from unmanaged to managed C++.



Working in Multiple Languages - I have a small consulting firm, and often our clients specify the programming language we are to use for a project. When several projects are on the go at once, I might switch languages several times in the course of a single day, as I help my associates with problems or track down the last few bugs in systems that are almost finished.

Before I starting using the .NET Framework, about once a month I'd suffer a "brain freeze" and for a moment or two forget how to perform some really simple task, like determining whether a string contains a particular character, in the language of the moment. Usually at those times my fingers would start typing the method or operator in randomly chosen other languages or libraries: Perl, Visual Basic, MFC, STL, Java—anything except the one I wanted. Now, with a common set of libraries across languages, I don't have to "context switch" nearly as often—and I avoid those "deer in the headlights" moments.


In the past, just because you knew how to perform a specific task, such as writing some text to a file in Visual C++, didn't mean you knew how to do that same task in Visual Basic. A great tutorial you found on database access in Visual Basic wasn't much help if you wanted to write your database application in Visual C++. Now, as long as you're working in managed C++, the walkthroughs, tutorials, and samples you find for any managed language—and you'll find plenty for Visual Basic and C#—are equally applicable to Visual C++. You'll have to translate the actual language elements, of course, but an explanation of a particular class or a method of that class is valid no matter which .NET supported language you intend to use.

Sending E-Mail with System.Web.Mail

Welcome to the next installment of the .NET Nuts & Bolts column. In this column, we'll explore sending e-mail from within applications. This will involve utilizing classes contained in the System.Web.Mail namespace.

Collaboration Data Objects

Collaboration Data Objects for Windows 2000 (CDOSYS) is a Microsoft messaging component that allows for standards-based e-mail messages to be constructed and sent. It is a replacement of the Collaboration Data Objects for NTS (CDONTS), which, as you can probably guess by the name, was for Windows NT. CDONTS is included with Windows 2000 for backwards compatibility, but Windows XP, Windows Server 2003, and beyond do not include or support the use of CDONTS. Thus, any applications that send messages using CDONTS must be migrated to use CDOSYS. It provides the same functionality and is just as easy to use.

In addition to serving as a replacement, CDOSYS introduces some new functionality that was not previously available in CDONTS. Some of the functionality includes:

The System.Web.Mail namespace contains classes that interact with CDOSYS to construct and send the message(s).

Using IIS and SMTP Service

In order for CDOSYS to send e-mail or other messages from your application, you need to enlist the services of IIS with the SMTP Service installed. Both are available in Windows 2000/XP through the Control Panel -> Add/Remove Programs -> Add/Remove Windows Components option. The job of the STMP Service is to accept and deliver the messages, based on the configuration. The service can attempt to deliver the messages directly, or it can utilize a smart host to deliver the message instead. When a smart host is enlisted, all messages are forwarded to it for delivery. You need to have IIS and the SMTP service installed and configured.

The SMTP Service uses a directory structure to contain messages prior to delivery. The default directory is C:\Inetpub\mailroot. This folder contains a number of subdirectories such as Queue, Drop, and Badmail. If you are unable to configure your instance of the SMTP Service for delivery, you can find the message in a *.EML file in the C:\Inetpub\mailroot\Queue directory. This technique can be useful when creating messages offline.

Sending a Message

As previously mentioned, sending an e-mail is a relatively simple thing to do. The System.Web.Mail.MailMessage class represents the message to be sent. E-mail messages are constructed by using instances of this class. This class contains properties such as To, From, and Subject that allow you to control the message being sent. Attachments can be created through instances of the System.Web.Mail.MailAttachment and then added to the MailMessage through the Attachments collection of the MailMessage. The message is then delivered through the System.Web.Mail.SmtpMail class.

Sending a Message Sample Code

The following sample code contains a C#-based Windows Console application that shows how to send an e-mail message. By not specifying that the SmtpMail.SmtpServer property is not set, the localhost is used by default. You need to make sure to add a reference to the System.Web.dll because this is a console application and not an ASP.NET application.

using System;
using System.Web.Mail;

namespace CodeGuru.SendMail
{
/// <summary>
/// Test console application to demonstrate sending e-mail.
/// </summary>
class TestMail
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
TestMail.Send("testuser@codeguru.com",
"mstrawmyer@crowechizek.com",
"Test Message Using CDOSYS",
"Hello World! This is a simple message sent
using CDOSYS.");
}

/// <summary>
/// Send a message using the .NET wrapper for Collaborative Data
/// Objects (CDO). This method should be used when sending to a
/// single recipient only; otherwise, the list of recipients
/// will be known.
/// </summary>
/// <param name="MessageFrom">Message originator</param>
/// <param name="MessageTo">Message receipent</param>
/// <param name="MessageSubject">Message subject</param>
/// <param name="MessageBody">Message body</param>
public static void Send(string MessageFrom,
string MessageTo,
string MessageSubject,
string MessageBody)
{
MailMessage message = new MailMessage();
message.From = MessageFrom;
message.To = MessageTo;
message.Subject = MessageSubject;
message.BodyFormat = MailFormat.Text;
message.Body = MessageBody;

try
{
System.Console.WriteLine("Sending outgoing message");
SmtpMail.Send(message);
}
catch( System.Web.HttpException exHttp )
{
System.Console.WriteLine("Exception occurred:" +
exHttp.Message);
}
}
}
}

Sending a Message with an Attachment Sample Code

The following sample code contains a C#-based Windows Console application that shows how to send an e-mail message that includes an attachment. This is done by creating instances of the MessageAttachment class and then adding them to the message through the Attachments collection.

using System;
using System.Web.Mail;

namespace CodeGuru.SendMail
{
/// <summary>
/// console application to demonstrate sending e-mail with an
/// attachment.
/// </summary>
class TestMail
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
TestMail.SendAttachment("testuser@codeguru.com",
"mstrawmyer@crowechizek.com",
"Test Message Using CDOSYS",
"Hello World! This is a simple
message sent using CDOSYS.",
"c:\\myattachment.txt");
}

/// <summary>
/// Send a message using the .NET wrapper for Collaborative Data
/// Objects (CDO). This method should be used when sending to
/// a single recipient only; otherwise, the list of recipients
/// will be known.
/// </summary>
/// <param name="MessageFrom">Message originator</param>
/// <param name="MessageTo">Message receipent</param>
/// <param name="MessageSubject">Message subject</param>
/// <param name="MessageBody">Message body</param>
/// <param name="MessageAttachmentPath">Path to attachment
/// </param>
public static void SendAttachment(string MessageFrom,
string MessageTo,
string MessageSubject,
string MessageBody,
string MessageAttachmentPath)
{
// Create and setup the message
MailMessage message = new MailMessage();
message.From = MessageFrom;
message.To = MessageTo;
message.Subject = MessageSubject;
message.BodyFormat = MailFormat.Text;
message.Body = MessageBody;

// Create and add the attachment
MailAttachment attachment = new
MailAttachment(MessageAttachmentPath);
message.Attachments.Add(attachment);

try
{
// Deliver the message
System.Console.WriteLine("Sending outgoing message");
SmtpMail.Send(message);
}
catch( System.Web.HttpException exHttp )
{
System.Console.WriteLine("Exception occurred:" +
exHttp.Message);
}
}
}
}

Possible Enhancements

We have demonstrated how to send e-mail messages in a couple of ways. It is now up to you to think about ways in which you can utilize this functionality within your applications. Here are some ideas to consider on your own:

Working with Math Routines in C#

Environment:  C#, .NET

Basic math operators—such as plus, minus, and modulus—can get you only so far. It is only a matter of time before you find that you need more robust math routines. C# has access to a set of math routines within the base classes. These are available from within the System.Math namespace. Table 1 presents a number of the math methods available.

 

The Math class is sealed. A sealed class cannot be used for inheritance. Additionally, all the classes and data members are static, so you can't create an object of type Math. Instead, you use the members and methods with the class name.

Table 1: Math Routines in the Math Class

Method
Returns

Abs
Returns the absolute value of a number.

Ceiling
Returns a value that is the smallest whole number greater than or equal to a given number.

Exp
Returns E raised to a given power. This is the inverse of Log.

Floor
Returns a value that is the largest whole number that is less than or equal to the given number.

IEEERemainder
Returns the result of a division of two specified numbers. (This division operation conforms to the remainder operation stated within Section 5.1 of ANSI/IEEE Std. 754-1985; IEEE Standard for Binary Floating-Point Arithmetic; Institute of Electrical and Electronics Engineers, Inc; 1985.)

Log
Returns a value that is the logarithmic value of the given number.

Log10
Returns a value that is the base 10 logarithm of a given value.

Max
Returns the larger of two values.

Min
Returns the smaller of two values.

Pow
Returns the value of a given value raised to a given power.

Round
Returns a rounded value for a number. You can specify the precision of the rounded number. The number .5 would be rounded down.

Sign
Returns a value indicating the sign of a value. 1 is returned for a negative number, 0 is returned for zero, and 1 is returned for a positive number.

Sqrt
Returns the square root for a given value.

Acos
Returns the value of an angle whose cosine is equal to a given number.

Asin
Returns the value of an angle whose sine is equal to a given number.

Atan
Returns the value of an angle whose tangent is equal to a given number.

Atan2
Returns the value of an angle whose tangent is equal to the quotient of two given numbers.

Cos
Returns a value that is the cosine of a given angle.

Cosh
Returns a value that is the hyperbolic cosine for a given angle.

Sin
Returns the sine for a given angle.

Sinh
Returns the hyperbolic sine for a given angle.

Tan
Returns the tangent of a specified angle.

Tanh
Returns the hyperbolic tangent of a given angle.

The Math class also includes two constants: PI and E. PI returns the value of [PI] as 3.14159265358979323846. The E data member returns the value of the logarithmic base, 2.7182818284590452354.

Most of the math methods in Table 1 are easy to understand. Listing 1 presents a couple of the routines in use.

Listing 1: MathApp.cs — Using Some of the Math Routines

 1:  //  MathApp.cs - Using a Math routine
2: //-----------------------------------------------
3: using System;
4:
5: class MathApp
6: {
7: public static void Main()
8: {
9: int val2;
10: char disp;
11:
12: for (double ctr = 0.0; ctr <= 10; ctr += .2)
13: {
14: val2 = (int) Math.Round( ( 10 * Math.Sin(ctr))) ;
15: for( int ctr2 = -10; ctr2 <= 10; ctr2++ )
16: {
17: if (ctr2 == val2)
18: disp = 'X';
19: else
20: disp = ' ';
21:
22: Console.Write("{0}", disp);
23: }
24: Console.WriteLine(" ");
25: }
26: }
27: }

The following is the output:

          X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X

This listing maps out the Sin method. A for statement in Lines 12 to 25 loops through double values, incrementing them by .2 each iteration. The sine of this value is obtained using the Math.Sin method in Line 14. The sine is a value from 1.0 to 1.0. To make the display easier, this value is converted to a value from 10 to 10. This conversion is done by multiplying the returned sine value by 10 and then rounding the value with the Math.Round method.

The result of doing the multiplication and rounding is that val2 is a value from 10 to 10. A for loop in Line 15 displays a single line of characters. This line of characters is spaces, with the exception of the character in the position equal to val2. Line 24 prints another space to start a new line. The result of this work is a rough display of a sine curve.

# # #

Using Application Configuration Files in .NET

Welcome to the next installment of the .NET Nuts & Bolts column. This article covers using application configuration files in Microsoft .NET. It briefly outlines the concept of application configuration files and touches on the native support the Microsoft .NET Framework provides. In particular, it discusses using the System.Configuration namespace among others within the Framework.

Application Configuration Background

Building configurable applications is not a new concept. The idea of applications relying on configuration parameters to dictate behavior at run time has been around for many years. For those of us who were around Microsoft-based programming prior to .NET, the most common example is the use of INI files. INI files are simple text-based files with key and value pairs; the key is used to fetch the value, and the value is then used in some way to influence the application settings and/or resulting behavior. This way, you can modify the configuration file to drive program behavior without having to recompile the application.

INI files generally worked great, except for the following drawbacks:

Microsoft moved away from INI files and towards storing everything in the Registry, which made application deployment much more difficult. As a result, Microsoft moved back to the concept of an "xcopy deployment," by which programmers deployed applications by simply copying files. Redmond used XML and some objects built into the Microsoft .NET Framework to bring new life to our old friend the application configuration file.

System.Configuration Basics

The System.Configuration namespace provides the functionality for reading configuration files. Microsoft released an XML schema dictating the format for configuration files that can be read using the System.Configuration API, enabling the accessing object to automatically consume the configuration files. This way, you can allow configuration files to be read without having to develop a bunch of plumbing to read the file and find the desired setting. Multiple types of configuration files are relevant to .NET, but this article focuses exclusively on the configuration files containing application-specific settings.

The name and storage location of an application configuration file depends on the application type with which it is being used. A configuration file for an executable (.exe) is located in the same directory as the application. The file is the name of the application with a .config extension. For example, notepad.exe would have a configuration file of notepad.exe.config. A configuration file for an ASP.NET application is called web.config and is located in the root of the application's virtual directory.

appSettings Section

An application configuration file follows a specific XML schema. The appSettings section is a predefined section of the configuration file designed to make it very easy to retrieve a value based on a given name. This is the easiest way to add application-specific settings into an application configuration file. The appSettings section of the configuration file consists of a series of "add" elements with "key" and "value" attributes. While the appSettings section is predefined, it is not included in a configuration file by default and must be manually added. A simple example of a configuration file would be the following:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="ApplicationTitle" value="Sample Console Application" />
<add key="ConnectionString"
value="Server=localhost;Database=Northwind;Integrated
Security=false;User Id=sa;Password=;" />
</appSettings>
</configuration>

The AppSettings property of the ConfigurationSettings object in the System.Configuration namespace is used to get the settings. For example, to read either of the settings above, the following sample code would do the trick:

// Read appSettings
string title = ConfigurationSettings.AppSettings["ApplicationTitle"];
string connectString =
ConfigurationSettings.AppSettings["ConnectionString"];

Now, you could easily set a dynamic title value or read database connection string information from the application configuration file. This gives you the freedom to adjust settings without having to recompile any code.

Customized Configuration Sections

If you have distinct groupings of configurations to be included in the application configuration file, consider creating a custom configuration section in lieu of the appSettings section. This will allow you more organizational structuring around the storage of various settings. You include a custom section by including the configSections element in the configuration file and a sub-element called section that defines the custom section and the handler for reading it. The .NET Framework contains an IConfigurationSectionHandler interface that dictates the required functionality handlers provide by allowing you to use custom handlers with the Configuration if you so choose. This example just uses handlers the .NET Framework already provides. Continuing the example from the previous section, the following configuration file contains a custom section and specifies which handler to use for reading:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="sampleSection"
type="System.Configuration.SingleTagSectionHandler" />
</configSections>

<sampleSection ApplicationTitle="Sample Console Application"
ConnectionString="Server=localhost;Database=Northwind;
Integrated Security=false;User Id=sa;
Password=;" />

<appSettings>
<add key="ApplicationTitle"
value="Sample Console Application" />
<add key="ConnectionString"
value="Server=localhost;Database=Northwind;
Integrated Security=false;User Id=sa;Password=;" />
</appSettings>
</configuration>

The following code reads the configuration settings into a dictionary, where you can easily access the values:

// Read customSection
System.Collections.IDictionary sampleTable = (IDictionary)
ConfigurationSettings.GetConfig("sampleSection");
string title = (string)sampleTable["ApplicationTitle"];
string connectString = (string)sampleTable["ConnectionString"];

About the Author
Mark Strawmyer, MCSD, MCSE, MCDBA is a Senior Architect of .NET applications for large and mid-size organizations. Mark is a technology leader with Crowe Chizek in Indianapolis, Indiana. He specializes in architecture, design and development of Microsoft-based solutions. Mark was honored to be named a Microsoft MVP for application development with C#. You can reach Mark at mstrawmyer@crowechizek.com.


 


Customized Configuration Sections and Groups

The issue with custom configuration sections alone is that they rely on attributes to store all of the settings as a part of a single element. This can become cumbersome when a large number of configurations are contained within a section or additional related sections. You can further organize similar custom sections into a section group, which makes it easier to work with numerous configurations and helps eliminate naming conflicts with custom configuration sections. This is especially useful where your application contains reference to several other DLLs that read configuration information from the host applications configuration file. A sectionGroup element is simply placed around one or more section elements to create a group. Expanding the example configuration file, the following configuration file contains a custom group, section, and the pre-defined appSettings:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="sampleSection"
type="System.Configuration.SingleTagSectionHandler" />
<sectionGroup name="CodeGuru">
<section name="utilitySection"
type="System.Configuration.NameValueSectionHandler,
System,Version=1.0.5000.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089" />
</sectionGroup>
</configSections>

<sampleSection ApplicationTitle="Sample Console Application"
ConnectionString="Server=localhost;Database=Northwind;
Integrated Security=false;User Id=sa;
Password=;" />

<CodeGuru>
<utilitySection>
<add key="ApplicationTitle"
value="Sample Console Application" />
<add key="ConnectionString"
value="Server=localhost;Database=Northwind;
Integrated Security=false;User Id=sa;
Password=;" />
</utilitySection>
</CodeGuru>

<appSettings>
<add key="ApplicationTitle"
value="Sample Console Application" />
<add key="ConnectionString"
value="Server=localhost;Database=Northwind;
Integrated Security=false;User Id=sa;Password=;" />
</appSettings>
</configuration>

 


Note the extra "Version," "Culture," and "PublicKeyToken" specified in defining a section group. These all come from what the machine.config configures on the local machine. Occasionally, it has worked for me without them, but other times an exception is thrown that it can't create the appropriate handler. I've found that if I include those items, it consistently works.

The following code can access the items in the custom group by loading the group into a NameValueCollection for easy access to the key and value pairs:

// Read custom sectionGroup
NameValueCollection collection = (NameValueCollection)
ConfigurationSettings.GetConfig("CodeGuru/utilitySection");
string title = collection["ApplicationTitle"];
string connectString = collection["ConnectionString"];

Note the path-like statement required to find the appropriate section to read when accessing the custom section.

Possible Enhancements

This article has touched on some of the basics of using application configuration files, including the ability to define your own sections and groups of sections. Here are a couple of additional items for you to ponder about application configuration settings:

IP Address Comparison and Conversion in C#

When using .NET and C#, IP Address information is stored in the System.Net.IPAddress class. The System.Net.IPAddress class provides no method for comparing IP addresses except for equality (==). There is no greater than or less then comparison operator available. In addition, the IPAddress class has a deprecated field "Address" which returns a long representation of the IP address. Because this function has been deprecated, I'll reimplement it here just for the sake of it.

 

This article provides examples for comparing IP addresses, converting to System.Net.IPAddress objects, and converting to uint representations so that comparisons can be performed.

Overview

This class provides functions for converting and comparing IP addresses. Something that is missing from the System.Net.IPAddress class. This class stands alone and therefore does not derive (as it could) from the System.Net.IPAddress class, nor does it implement any operator overloading. This prevents the need to actually instantiate classes for these simple operations.

This code is provided free for modification without warrantee, so please refactor it as you need. The purpose of this class is for example/educational purposes.

Code

The class for performing IP conversion and comparison functions is self contained and is self describing. Any ambiguity is documented as comments in the source.

using System;
using System.Net;

namespace LoweSoftware.Tools
{
/// <summary>
/// Used to convert and compare IP addresses.
///
/// By Alexander Lowe, Lowe*Software.
/// http://www.lowesoftware.com
///
/// Free for use and modification. No warranty express or
/// implied.
/// </summary>
class IPCompare
{
/// <summary>
/// Compares two IP addresses for equality.
/// </summary>
/// <param name="IPAddr1">The first IP to compare</param>
/// <param name="IPAddr2">The second IP to compare</param>
/// <returns>True if equal, false if not.</returns>
static public bool AreEqual(string IPAddr1, string IPAddr2)
{
// convert to long in case there is any zero padding in
// the strings
return IPAddressToLongBackwards(IPAddr1)==
sIPAddressToLongBackwards(IPAddr2);
}

/// <summary>
/// Compares two string representations of an Ip address to
/// see if one is greater than the other
/// </summary>
/// <param name="ToCompare">The IP address on the left hand
/// side of the greater than operator</param>
/// <param name="CompareAgainst">The Ip address on the right
/// hand side of the greater than operator</param>
/// <returns>True if ToCompare is greater than CompareAgainst,
/// else false</returns>
static public bool IsGreater(string ToCompare,
string CompareAgainst)
{
// convert to long in case there is any zero padding in
// the strings
return IPAddressToLongBackwards(ToCompare)>
IPAddressToLongBackwards(CompareAgainst);
}

/// <summary>
/// Compares two string representations of an Ip address to
/// see if one is less than the other
/// </summary>
/// <param name="ToCompare">The IP address on the left hand
/// side of the less than operator</param>
/// <param name="CompareAgainst">The Ip address on the right
/// hand side of the less than operator</param>
/// <returns>True if ToCompare is greater than CompareAgainst,
/// else false</returns>
static public bool IsLess(string ToCompare,
string CompareAgainst)
{
// convert to long in case there is any zero padding in
// the strings
return IPAddressToLongBackwards(ToCompare)<
IPAddressToLongBackwards(CompareAgainst);
}


/// <summary>
/// Compares two string representations of an Ip address to
/// see if one is greater than or equal to the other.
/// </summary>
/// <param name="ToCompare">The IP address on the left hand
/// side of the greater than or equal operator</param>
/// <param name="CompareAgainst">The Ip address on the right
/// hand side of the greater than or equal operator</param>
/// <returns>True if ToCompare is greater than or equal to
/// CompareAgainst, else false</returns>
static public bool IsGreaterOrEqual(string ToCompare,
string CompareAgainst)
{
// convert to long in case there is any zero padding in
// the strings
return IPAddressToLongBackwards(ToCompare)>
=IPAddressToLongBackwards(CompareAgainst);
}

/// <summary>
/// Compares two string representations of an Ip address to
/// see if one is less than or equal to the other.
/// </summary>
/// <param name="ToCompare">The IP address on the left hand
/// side of the less than or equal operator</param>
/// <param name="CompareAgainst">The Ip address on the right
/// hand side of the less than or equal operator</param>
/// <returns>True if ToCompare is greater than or equal to
/// CompareAgainst, else false</returns>
static public bool IsLessOrEqual(string ToCompare,
string CompareAgainst)
{
// convert to long in case there is any zero padding in
the strings
return IPAddressToLongBackwards(ToCompare)<
=IPAddressToLongBackwards(CompareAgainst);
}



/// <summary>
/// Converts a uint representation of an Ip address to a
/// string.
/// </summary>
/// <param name="IPAddr">The IP address to convert</param>
/// <returns>A string representation of the IP address.</returns>
static public string LongToIPAddress(uint IPAddr)
{
return new System.Net.IPAddress(IPAddr).ToString();
}

/// <summary>
/// Converts a string representation of an IP address to a
/// uint. This encoding is proper and can be used with other
/// networking functions such
/// as the System.Net.IPAddress class.
/// </summary>
/// <param name="IPAddr">The Ip address to convert.</param>
/// <returns>Returns a uint representation of the IP
/// address.</returns>
static public uint IPAddressToLong(string IPAddr)
{
System.Net.IPAddress oIP=System.Net.IPAddress.Parse(IPAddr);
byte[] byteIP=oIP.GetAddressBytes();


uint ip=(uint)byteIP[3]<<24;
ip+=(uint)byteIP[2]<<16;
ip+=(uint)byteIP[1]<<8;
ip+=(uint)byteIP[0];

return ip;
}


/// <summary>
/// This encodes the string representation of an IP address
/// to a uint, but backwards so that it can be used to
/// compare addresses. This function is used internally
/// for comparison and is not valid for valid encoding of
/// IP address information.
/// </summary>
/// <param name="IPAddr">A string representation of the IP
/// address to convert</param>
/// <returns>Returns a backwards uint representation of the
/// string.</returns>
static private uint IPAddressToLongBackwards(string IPAddr)
{
System.Net.IPAddress oIP=System.Net.IPAddress.Parse(IPAddr);
byte[] byteIP=oIP.GetAddressBytes();


uint ip=(uint)byteIP[0]<<24;
ip+=(uint)byteIP[1]<<16;
ip+=(uint)byteIP[2]<<8;
ip+=(uint)byteIP[3];

return ip;
}
}
}

References

System.Net.IPAddress Class Reference, MSDN

Download :


IPCompare.zip - IPCompare class source file.

Enterprise Library 2.0: The Logging Application Block

The Enterprise Library 2.0 (EL2) is the second major release of the robust frameworks released by the Microsoft Patterns and Practices team. At the time this article was written, EL2 is a "Community Technology Preview (CTP)", so features are subject to change.

EL2 consists of six main application blocks: Logging and Instrumentation, Caching, Data Access, Cryptography, Security, and Exception Handling. Along with the application blocks, EL2 is built on a number of core components, which can be consumed by the general public, but are primarily used internally by the application blocks. These core components can be classified as: Configuration, Instrumentation, and the ObjectBuilder. In the previous version of the Enterprise Library, Configuration was the seventh application block. In EL2, the functionality has been replaced by the .NET 2.0 System.Configuration namespace and has been moved into the core architecture.

Although EL2 will ship with a series of excellent "QuickStart" applications, these quick starts don't always explore all the features and have been known to lack sufficient documentation. In addition, logging (for example, debug, trace, error, and so on) is far from standardized in most organizations, making it important to have as much content on the subject as possible. For example, a number of organizations have multiple ASP.NET applications that all log information differently.

The Logging Application Block and EL2 are not turnkey solutions; however, they provide people with a mechanism to create a unified logging strategy. The goal of this article is to investigate some basic logging scenarios, explain and define some of the required terminology, and help you take those first steps to more efficient logging.

Major Changes in the Logging Block

There are too many changes in the Logging Application Block between the Enterprise Library (EL1) and EL2 to cover in this short article, but I want to highlight some of the major changes in the Logging Application Block.

First, the previous version of the Logging Application Block (in fact, most of the original version of EL1) was based on Avanade's ACA.NET framework. The word "based" is used very loosely, because in many cases the only change between ACA.NET and EL1 is the namespace. Although ACA.NET and EL1 are both great pieces of code, EL2 has simplified the logging strategy and has aligned better with the .NET 2.0 framework.

For example, Trace Listeners (A.K.A. LogSinks in previous versions) were custom code. In the new version, they derive from the very established TraceListener class in the System.Diagnostics namespace. Additionally, the frequently used LogEntry class now supports multiple categories. This may not sound like a big deal; however, this feature allows you to create a more standardized logging policy across applications. Multiple categories provide you with the ability to log different event types (for example, debug, audit, and so forth) specified in different application tiers (as in UI, Business, and the like) to different locations (for example, database, text, and so on), a big improvement from previous versions.

Along with the code improvements, the new logging application block has also simplified the "strategy" aspect. This was done by eliminating the concept of "distribution strategies" that were tedious and complicated in EL1. Instead, EL2 utilizes a far superior architecture of linking TraceListener classes together, which further aligns its design with the .NET 2.0 frameworks.

Logging Block Definitions

Before jumping into some examples of working with the EL2 Logging Application Block, you should first understand some terminology. The following is a summary of some of the definitions that you will need to know before using the Logging Application Block:

Jumping into Logging...

The best way to understand EL2's logging features is to jump right into a few examples. Let me set the stage a bit. The first example is very simple and its purpose is to get the wheels spinning; it demonstrates a simple database logging scenario. The second example extends the logging application block. Both examples demonstrate the extensibility points in EL2.

I assume you have the EL2 already. If not, you can obtain it from here. If you are using the December CTP, it is highly recommended that you build the EL2 configuration console prior to working with the examples in this article. The configuration console allows you to manipulate the configuration files through a simple GUI.

Along with the configuration console, the following examples require that you have Visual Studio 2005 and a MS SQL Server database installed. I used SQL 2005 Express, but I believe that, with some minor tweaks, any version should work.

EL2 Database Logging

To start the first example showing a simple database logging, navigate to the install directory of the Enterprise Library 2.0 and locate the "..\Src\Logging\TraceListeners\Database\Scripts\" directory. In this directory, you should find a batch file, "CreateLoggingDb.cmd," which will need to be executed (first ensure that SQL Server is running). This batch file executes the SQL script, which is located in the same directory and called LoggingDatabase.sql, creating the logging database natively used by EL2. The native logging database used in this example, "Logging," is very full-featured, but you do also have the choice to design and create your own database for use with the logging block.

Continue by creating a new C# Windows application in Visual Studio 2005; for ease of use, for this and all of the examples, Windows forms applications will be used. Add a new application configuration file, and open the Enterprise Library console.

EL2 is heavily driven through meta-data stored in application configuration files. The console creates a visual interpretation of the XML stored in these configuration files and makes it easy to manipulate the data. Navigate to File, Open Application, and select the app.config file that you just created. Another way to do this is to create the app config file in the EL2 console, but both methods have the same result.

The next step is to add a new node to the configuration by right-clicking (or navigating to "Action") in the console on the top-level node, and selecting "Logging Application Block."

Figure 1: The EL2 console is a straightforward GUI that reduces the time it takes to generate the required configuration information.

By default, the Logging application block gets configured with the EventLog trace listener enabled for the "General" category, and the "Special Source" category of "Logging Errors & Warnings." The event log is a fine logging store if you have a single server, but in most cases you want something more centralized that you can query against, such as a database.

For this example, you will need to remove all of the event log references by highlighting their nodes and going to "Action, Remove" or by right-clicking on their nodes and selecting "Remove." Add a new "Database Trace Listener" by highlighting the "Trace Listeners" node by right-clicking or going to "Action" again. You should see a new database trace listener and a new reference to the data access application block. The granular details about the data access block will be saved for another time, but do know that a new connection reference (which defaults to "Connection String") has been created. The setting attributes will need to be modified appropriately to reflect your database setup.

The next step is to associate the "Database Trace Listener" with the new database connection reference, and provide it a formatter; in this case, the default text formatter is used.

The final configuration step is to add a new category called "Audit" under "Category Sources" and reference the new "Database Trace Listener."

Rest assured, even though these steps are slightly time consuming, they are far simpler than editing the configuration files by hand. Downloading the example will help make sense of the process.

With the application configuration out of the way, the next step is very simple. Just add a reference in VS 2005 to Microsoft.Practices.EnterpriseLibrary.Logging, add the using statement (Using Microsoft.Practices.EnterpriseLibrary.Logging), and add the following code to a button:

LogEntry le = new LogEntry();
le.Categories.Add("Audit");
le.Message = "Testing our DB Logging";
le.EventId = 1234;
le.Title = "Database Message";
le.Priority = 1;
Logger.Write(le);

Downloads

  • EL2LoggingProject.zip - Download source code - 19 Kb

  •  


    Extending the Logging Application Block

    EL2 and the Logging Application Block are highly extensible frameworks that have many predefined extension points. In this next example, you'll get a closer look at one of the extension points, the Custom Trace Listener, by creating a rolling flat file trace listener.

    Note: The purpose of this next example is to demonstrate the extensibility of the logging application block. The example has not been tested for production use and probably is not the most efficient method for creating a rolling log file.


     


    At this point, it is important that you download the source code so you can follow along with the text. I am not going to present all of the details on the System.Diagnostic.TraceListener class because there is already a ton of information out there on MSDN. I will outline the necessary steps required to create a custom trace listener in the context of the Logging Application Block.

    First, to wire up a custom trace listener and to enable it for use with the Console, you must do two things: inherit from Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.CustomTraceListener class and decorate your new class with the following attribute:

    [ConfigurationElementType(typeof(CustomTraceListenerData))]

    Now, understand that you can manually go through and hack your application configuration files, so technically speaking the attribute is not absolutely necessary, but it is a significant time saver.

    After you create a new class that meets those two requirements, it is as easy as opening the console and configuring your "Custom Trace Listener." This is accomplished the same way you configured the Database Trace Listener with two exceptions. First, you will have to specify the "type" attribute (right pane of the console), which is the location of the assembly with the Custom Trace Listener. Second, for this example you will need to configure the attribute collection, which is a group of name value parameters for use at runtime:


    Figure 2: Use the EL2 console to configure name-value pairs for use by the Custom Trace Listener.

    In this example, you are creating a new attribute called "fileName" and giving it a value of "CustomLog{Date}.txt". The "{Date}" string is used by the custom TraceListener as a token to enable the string as a replacement value. The attribute system is actually not new with EL2; rather, .Attribute is a StringDictionary property that hangs on the System.Diagnostic.TraceListener base class.

    What's new with EL2 is the plumbing code that automatically interprets the information stored in the app.config file. Take a look at the following excerpt from the app.config file. As you can see, the attribute "fileName" has been added to the listener configuration section automatically, and is interpreted natively by EL2.

    <listeners>
    <add fileName="CustomLog{Date}.log" listenerDataType="..."
    name="Custom TraceListener" initializeData="" />
    ...
    </listener>

    The last thing you need to do before writing some code to use this new trace listener is create a new category called "Fatal." Enable the new "Fatal" category with the new Custom Trace Listener. Once you have saved, add some code to test out the new Custom Trace Listener. Rather than logging to a single category (as in the previous example), use one of the new features in the Logging Block by utilizing the two categories created so far, "Audit" and "Fatal":

    LogEntry le = new LogEntry();
    le.Message = "Testing both our Custom Trace Listener and our
    Database Trace Listener";
    le.Categories.Add("Audit");
    le.Categories.Add("Fatal");
    le.EventId = 1234;
    le.Title = "Two Categories";
    le.Priority = 1;
    Logger.Write(le);

    Logger.Write() has 19 overloads, so nearly every way you would want to log a message (in other words, Generics, string parameters, and so forth) is available.

    The Easier, the Better

    This article has only scratched the surface of the new Logging Application block, but I hope that it has gotten some of you thinking about how EL2 can improve your code. The Enterprise Library is a free, reusable, battle-tested set of frameworks that can greatly reduce your time to market or improve your existing code base.

    Related Links

    New Windows Event Log: Gateway to Native Windows Functionality in Vista

    While the .NET Framework's feature coverage continues to increase—particularly with the WinFX release in Windows Vista, native code will continue to be the most powerful and flexible mechanism for producing applications that work closely with the host operating system (OS). To demonstrate that native development is alive and well in Vista, this article covers the new Windows Event Log features to prepare you for a plunge into the native libraries, which are the only way to access the new functionality.

     

    Since Microsoft unveiled .NET at the Professional Developers Conference in 2000, where native development fits in has been an ongoing debate. Extreme opinions have been expressed on both sides of the argument, ranging from managed code is a toy technology that will never be suitable for real-world application development to developers never need to write another line of native code!

    The truth obviously rests between these two opinions, and Microsoft's first major revision of the Windows OS since the release of .NET (Windows Vista) provides some hard evidence on which to examine the native/.NET divide.

    Based on the feature set growth of the managed WinFX libraries, compared to the native Windows SDK libraries, Microsoft is continuing to focus its managed code efforts on providing the best platform for application development and leaving native code as the preferred offering for system development. At its heart, Vista is simply another release of the well-known NT kernel with a number of additional libraries for both native and managed development. The compilers, which allow native libraries to be called for managed applications and vice-versa, reflect Microsoft's strategy of releasing a library using the most appropriate technology for the functionality of the library.

    With Vista, managed code becomes a core part of the Windows offering, and a number of new technologies are exposed primarily through managed libraries. You can call these libraries from native C/C++ through COM interoperability, but it is generally easier and faster (both in terms of development speed and execution speed) to call managed libraries using the /clr compiler option at a minimum and preferably using C++/CLI.

    Author's Note: Not all the new functionality is available through WinFX, and native libraries continue to be the only way to access some aspects of the operating system's functionality.

    Windows Event Logging

    Event logging prior to Vista's new event logging framework was a complicated proposition. A wide range of operating system features offered competing functionality, and no logging technology offered all the features an application may have wished to use. The following four main technologies available to native developers were:

    One of the main problems of all these event-logging technologies is that they have minimal built-in security. Given the potentially sensitive information that a log file can contain and the denial-of-service possibility of spoofed log events, incorporating security in a logging mechanism is an important consideration.

    The New Event Tracing Framework

    Rather than throw out all the logging technologies currently available, Vista uses these as building blocks for a new uber-framework. Starting at the most visible manifestation of the logging changes, Figure 1 shows the new Event Viewer for Windows Vista. Despite some superficial similarities to the Windows XP event log, the Vista Event Log offers a wide range of functionality traditionally associated with an enterprise-monitoring tool.


    (
    Full Size Image)

    Figure 1. Windows Vista Event Viewer

    Starting on the left pane, the Event Viewer still has the familiar Windows Logs, where events from the Windows XP-style event logging API will end up. One of the new features of the left pane is the ability to define a standard query of log events that can be persisted as a custom view. This can be incredibly useful for operations staff who are interested only in events of a certain severity from a particular event source. The other new feature is the hierarchal presentation of custom application logs. While you could create custom logs with the old Event Logging framework, it had no concept of log hierarchies.

    The main pane of the Event Viewer shows the list of events in a list-view (as is the case in XP). It also includes a new preview pane that negates the need to bring up a dialog in order to inspect the details of the event. It is still possible to bring up a dialog box, and the dialog box in Vista will not be modal, enabling you to bring up multiple details dialog boxes and compare events. The Event Viewer also presents an XML view of the data in an event record, and you can query all the events across multiple logs with XPath.

    The Actions Pane on the right presents various options for opening, filtering, and closing various filters. One of the more interesting features is the ability to attach a task to a specific event. The task can be any Windows Task Scheduler task. You can use this feature to alert operations staff that need to respond to any event or even to respond to an event automatically.

    The new event logging framework for Windows Vista is a part of the Vista operating system, and Microsoft will not back-port it to Windows XP or Windows Server 2003.

     

    The new Event Logging in Vista is built on top of ETW. ETW was the most comprehensive and robust logging framework in the NT5.x kernel, and building on top of it allowed the Vista team to concentrate on adding new functionality rather than re-inventing an existing logging framework.

    The Windows Event Log uses channels to deliver events to a log file. Vista ships with a number of pre-defined channels for Application, Security, Setup, and System Windows Event logs (these are visible in the left pane in Figure 1), and new applications that target Vista will each add a new channel. The bottom of the left pane in Figure 1 also shows custom channels created by the various components of the Vista operating system.

    Channels come in two distinct types: serviced channels and direct channels. Having two different types satisfies the two distinct reasons you may log an event. For high-performance, high-volume logging to inspect events on a log console as they flow past, use the direct channel. Direct channel events are not processed by the Event Logging framework, and you cannot subscribe to an event from a direct channel. In contrast, serviced channels offer the reliable delivery of messages that you can subscribe to via an XPath query. Serviced channel events can be either:

    Introducing LINQ for Visual Basic

    C# 3.0 incorporates LINQ (Language INtegrated Query), a cool technology that adds dynamic queries to all kinds of objects in a familiar SQL-like style. LINQ code looks like embedded SQL statements, which I actually don't care for because SQL has never looked pretty to me. I also do not believe that LINQ adds clarity to code. It looks terse and somewhat ambiguous. In a nutshell, LINQ is a sexy technology (as they are fond of saying in Redmond), but it is not pretty.

    This article shows you how to get LINQ for Visual Basic and begin using it now. As you read, you can begin formulating your own opinion of its elegance and usefulness.

    Using LINQ for VB Now

    Begin by downloading and installing LINQ. This article uses the May 2006 CTP (Community Technology Preview).

    Tip: If you like bleeding-edge technologies and want to know what Microsoft is working on for future products, check out research.microsoft.com. LINQ, for example, came out of the Omega research project.

    As with all pre-release software, you download and install with some risk. In this case, LINQ will cause minor problems with IntelliSense and CodeRush. If you rely on CodeRush, Refactor, and IntelliSense, then install LINQ on a Virtual PC instance with Visual Studio 2005 on it.

    Working with the LINQ CTP

    Once you have downloaded and installed LINQ, you will notice a new folder named C:\Program Files\LINQ Preview. This folder contains LINQ documentation, assemblies, and examples. When you run Visual Studio after installing LINQ, you will get a message indicating LINQ for C# is installed, but LINQ for VB also will be installed.

    To use LINQ and create a project, pick one of the LINQ project templates as shown in Figure 1. These project templates will add the appropriate references to LINQ assemblies and any necessary import statements.


    (Full Size Image)

    Figure 1: LINQ Project Templates

    All of the samples in this article are part of a console application, and the referenced assemblies—including Microsoft.VisualBasic.LINQ, System.Data.DLinq, and System.Xml.XLinq—will be added to your project's list of references (see Figure 2). In fact, LINQ is supported for ADO and XML too.

    Figure 2: References to Essential LINQ Assemblies

    Tip: To view the references in a VB project, click the Show all files button (highlighted in blue in Figure 2) in the Solution Explorer.

    Having installed LINQ, let's look at some of the things you can do with this technology.

    Using LINQ

    LINQ is a query language for .NET—VB in the examples to follows. To be useful and intuitive, I would expect LINQ to do almost everything SQL can do. Although I didn't test its limits, LINQ proved pretty intuitive as I developed the examples. I literally guessed at them based on what I anticipated from a query language. The following sections discuss some of the queries you can write with LINQ.

    Selecting Numbers from an Array

    LINQ for VB looks a little like reverse polish notation for SQL. For example, you can define an array of integers and then query that array using a WHERE predicate to return a subset of integers from an unordered set. Listing 1 shows a VB subroutine that does just that with a subset of fewer than 10 integers.

    Listing 1: Select Numbers from an Array of Unordered Integers Using LINQ

    Sub SampleOne()
    Dim numbers = {2, 1, 34, 5, 8, 13, 21, 33}

    Dim lessThanTen = From num In numbers _
    Where num < 10 _
    Select num


    For Each Dim n In lessThanTen
    Console.WriteLine(n)
    Next

    Console.ReadLine()

    End Sub

    This example defines an array of random integers. The LINQ query (shown in bold) defines an anonymous type lessThanTen, which becomes an instance of IEnumerable. (Anonymous types are much more spectacular to C# programmers; you VB programmers are used to this type of notation.) The LINQ query From num in numbers Where num < 10 Select Num initializes lessThanTen. And, of course, most of us know by now that any IEnumerable type can be used in a For Each statement. The result from Listing 1 are the numbers 1, 2, 5, and 8 printed to the console.

    Notice that numbers uses an array initializer without explicitly declaring numbers as an array (using numbers()). The variable numbers's type is an anonymous type whose actual type is inferred. The same is true of the anonymous variable n. (Note the unusual location of the keyword Dim in the For Each statement. This is valid .NET 3.0 code.)

    Using LINQ

    LINQ is a query language for .NET—VB in the examples to follows. To be useful and intuitive, I would expect LINQ to do almost everything SQL can do. Although I didn't test its limits, LINQ proved pretty intuitive as I developed the examples. I literally guessed at them based on what I anticipated from a query language. The following sections discuss some of the queries you can write with LINQ.

    Selecting Numbers from an Array

     


    LINQ for VB looks a little like reverse polish notation for SQL. For example, you can define an array of integers and then query that array using a WHERE predicate to return a subset of integers from an unordered set. Listing 1 shows a VB subroutine that does just that with a subset of fewer than 10 integers.

    Listing 1: Select Numbers from an Array of Unordered Integers Using LINQ

    Sub SampleOne()
    Dim numbers = {2, 1, 34, 5, 8, 13, 21, 33}

    Dim lessThanTen = From num In numbers _
    Where num < 10 _
    Select num


    For Each Dim n In lessThanTen
    Console.WriteLine(n)
    Next

    Console.ReadLine()

    End Sub

    This exampe defines an array of random integers. The LINQ query (shown in bold) defines an anonymous type lessThanTen, which becomes an instance of IEnumerable. (Anonymous types are much more spectacular to C# programmers; you VB programmers are used to this type of notation.) The LINQ query From num in numbers Where num < 10 Select Num initializes lessThanTen. And, of course, most of us know by now that any IEnumerable type can be used in a For Each statement. The result from Listing 1 are the numbers 1, 2, 5, and 8 printed to the console.

    Notice that numbers uses an array initializer without explicitly declaring numbers as an array (using numbers()). The variable numbers's type is an anonymous type whose actual type is inferred. The same is true of the anonymous variable n. (Note the unusual location of the keyword Dim in the For Each statement. This is valid .NET 3.0 code.)

    Using Ordering Clauses in LINQ

    Listing 2 defines an unordered array of Fibonacci numbers and uses an ORDER clause to order the array. (Google Fibonacci for a definition. You will find these numbers pretty interesting.)

    Listing 2: Ordering an Array of Integers Using LINQ

    Sub SampleTwo()
    Dim numbers = {2, 1, 34, 5, 8, 13, 21, 3}

    Dim ordered = From num In numbers _
    Select num _
    Order By num


    For Each Dim n In ordered
    Console.WriteLine(n)
    Next

    Console.ReadLine()

    End Sub

    Listing 2 is like Listing 1 but with the addition of the Order By clause. The anonymous type ordered contains the re-ordered list of integers: 1, 2, 3, 5, 8, 13, 21, 34. Adding the keyword Descending after the clause Order By num will sort the list in reverse order. This variant is shown in Listing 3.

    Listing 3: Sorting in Reverse Order

    Sub SampleThree()
    Dim numbers = {2, 1, 34, 5, 8, 13, 21, 33}

    Dim ordered = From num In numbers _
    Select num _
    Order By num Descending

    For Each Dim n In ordered
    Console.WriteLine(n)
    Next

    Console.ReadLine()
    End Sub

    Sorting Generic Lists of Custom Types

    You can also apply LINQ to custom types. Suppose I define a custom type Customer (see Listing 4) and want to sort a generic list of customers.

    Listing 4: A User-Defined Customer Class

    Public Class Customer

    Private fid As Integer
    Private fname As String
    Private fcity As String

    Public Sub New(ByVal id As Integer, ByVal name As String, _
    ByVal city As String)
    fid = id
    fname = name
    fcity = city
    End Sub

    Public Overrides Function toString() As String
    Dim mask As String = "{0} is in {1}"
    Return String.Format(mask, fname, fcity)
    End Function


    Public Property ID() As Integer
    Get
    Return fid
    End Get
    Set(ByVal value As Integer)
    fid = value
    End Set
    End Property

    Public Property Name() As String
    Get
    Return fname
    End Get
    Set(ByVal value As String)
    fname = value
    End Set
    End Property

    Public Property city() As String
    Get
    Return fcity
    End Get
    Set(ByVal value As String)
    fcity = value
    End Set
    End Property

    End Class

    I can define the Customer class and instantiate a List(Of T) where T is a customer using a specific field from the class Customer (see Listing 5).

    Listing 5: Initializing a Generic List of Customer Objects and Sorting Based on the City Field

    Sub SampleFour()

    Dim customers = New List(Of Customer)
    customers.Add(New Customer(1, "Kimmel Computers", "Plattsburgh"))
    customers.Add(New Customer(2, "Fred's Frieds", "Memphis"))
    customers.Add(New Customer(3, "House of Clams", "Detroit"))

    Dim ordered = From cust In customers _
    Select cust _
    Order By cust.City


    For Each Dim c In ordered
    Console.WriteLine(c.toString())
    Next

    Console.ReadLine()

    End Sub

    Implementing IComparable

    If you modify Customer by implementing Icomparable, you can tell Customer objects how to compare themselves. Implement IComparable by adding the Implements IComparable statement to the Custom class and adding the Function CompareTo. Listing 6 shows the revised Customer class, and Listing 7 shows the revised code from Listing 5 that now uses LINQ to sort based on the implementation of IComparable.

    Listing 6: Implement IComparable to Compare Customer Objects by City

    Public Class Customer
    Implements IComparable

    Private fid As Integer
    Private fname As String
    Private fcity As String

    Public Sub New(ByVal id As Integer, ByVal name As String, _
    ByVal city As String)
    fid = id
    fname = name
    fcity = city
    End Sub

    Public Overrides Function toString() As String
    Dim mask As String = "{0} is in {1}"
    Return String.Format(mask, fname, fcity)
    End Function


    Public Property ID() As Integer
    Get
    Return fid
    End Get
    Set(ByVal value As Integer)
    fid = value
    End Set
    End Property

    Public Property Name() As String
    Get
    Return fname
    End Get
    Set(ByVal value As String)
    fname = value
    End Set
    End Property

    Public Property city() As String
    Get
    Return fcity
    End Get
    Set(ByVal value As String)
    fcity = value
    End Set
    End Property

    Public Function CompareTo(ByVal obj As Object) _
    As Integer Implements System.IComparable.CompareTo
    Return city.CompareTo(obj.City)
    End Function

    End Class

    Listing 7: Sorting a Generic List of IComparable Customer Objects

    Sub SampleFive()
    Dim customers = New List(Of Customer)
    customers.Add(New Customer(1, "Kimmel Computers", "Plattsburgh"))
    customers.Add(New Customer(2, "Fred's Frieds", "Memphis"))
    customers.Add(New Customer(3, "House of Clams", "Detroit"))

    Dim ordered = From cust In customers _
    Select cust _
    Order By cust


    For Each Dim c In ordered
    Console.WriteLine(c.toString())
    Next

    Console.ReadLine()
    End Sub

    The only difference between Listing 7 and Listing 5 is that the Order By clause now specifies that you are sorting only Customer objects. The sort field is implicit as defined by the CompareTo function.

    A SQL-Like Query Language

    This article introduced LINQ and demonstrated how you can combine this technology with existing technologies such as interfaces and generic lists to support a SQL-like query language in your VB.NET code. Of course, LINQ (and .NET 3.5) introduce many more capabilities, including anonymous types and support for ADO.NET and XML. Go ahead and explore.