Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Visual CSharp .NET Developer's Handbook (2002) [eng]

.pdf
Скачиваний:
35
Добавлен:
16.08.2013
Размер:
4.94 Mб
Скачать

The application starts by validating the current service status in the MainForm_Activated() method. Any application that provides access to a service needs to make this check. Otherwise, you have no idea of what state the service is in and whether it's safe to work with it. Multiple users could attempt to access the service at the same time. If User A stops the service at the same time User B wants to access it, one or both calls could fail.

The bntStart_Click() and btnStop_Click() methods control the service status. You can always attempt to start a service, but some services won't allow you to stop them. The btnStop_Click() contains additional code that verifies that the service can stop before it attempts to stop the service. Notice that both methods contain a call to WaitForStatus(). This call ensures that the user is unable to do anything more with the application until the service reaches a stable state. The ServiceControllerStatus enumeration contains a number of standard service states. You must also use one of the members from the TimeSpan class to specify how long to wait for the service to change states. The example uses the From-Milliseconds() method, but any of the methods will work. Never use the infinite wait version of WaitForStatus() because the application could freeze. If the service hasn't changed states in the time provided, your application should display an error message and offer the user some options to overcome the problem.

The btnAccess_Click() method is the first to use the arcane command system used by Windows services. Notice that there's nothing to indicate the purpose of the command. You must simply know that executing command number 128 will update the number of accesses count in the service.

The ExecuteCommand() returns type void. You have no idea if the service executed the command successfully unless you look in the event log to see if there's an entry in either the System or Application logs. The ExecuteCommand() method also provides no means for returning data from the service, so you need to provide your own method.

The btnGetAccess_Click() enables the user to learn how many times the service has been accessed since it started. As you can see from Listing 7.7, the application must go to some trouble to gain access to the required information. In this case, it uses the event log as a medium for exchange. The CategoryNumber and EventID property combination is unique and unlikely to cause problems with any other entry in the event log. (Even so, you could perform additional checks on the event entry before you process it.) Figure 7.7 shows typical event log entries from the service when accessed by the client.

Figure 7.7: The client and service rely on the event log for communication.

Spending time looking through the series of event log entries in both the System and Application logs will help you better understand the way services function. Notice the special

event with the Category value of (99). Figure 7.8 shows the message associated with this entry. Notice that the service provided the number of accesses in an easy to read form.

Figure 7.8: Event log messages must be easy to understand because they're the sole source of service communication.

Understanding Critical Sections

A critical section is a piece of code or a variable that application threads can only access one thread at one time. If two applications require access to the same critical section, the first to make the request will obtain access. The second application will wait until the first application completes its task. In short, critical sections create bottlenecks in your code and can affect performance if you're not careful.

Some forms of critical section ensure that the thread completes code sequence without interruption. For example, you wouldn't want to begin a save to a database and have another thread of execution interrupt that save. The first application must complete the save before a second thread starts in order to ensure data integrity. The .NET Framework doesn't provide special calls to perform magic in this case; you must develop the thread in such a way that it saves the data safely. In short, the critical section helps ensure database integrity.

You may want to create a critical section for many different reasons; the most important of which is application data integrity. An application changes the contents of variables and the status of objects to meet the needs of a particular user. If another user suddenly decides to execute the same code, the lack of a critical section to protect the variable and object content would ruin the application for both parties.

A critical section must protect both data and code. The variables used in the thread are of equal importance to executing the code in the desired sequence. There are two methods used to create a critical section for use with threads. The first is to use the C# lock keyword to create a critical block. The second method is to use the Enter() and Exit() methods of the Monitor class. The second method is actually preferred because it provides you with greater flexibility and it isn't C#-specific because it relies on a .NET Framework class.

Let's look at a potential problem involving critical sections. Here's a modified version of the btnNewThread_Click() method we discussed in Listing 7.5. (You can find the source code for this example in the \Chapter 07\Critical Section folder of the CD.)

private void btnNewThread_Click(object sender, System.EventArgs e)

{

//Create a new thread object. DLLThread.DLLThread MyThread =

new DLLThread.DLLThread(txtDlgName.Text);

//Set the interval.

MyThread.Interval = 5000;

//Create and start the new thread. Thread DoThread =

new Thread(new ThreadStart(MyThread.CreateUIThread)); DoThread.Start();

//Change the interval.

MyThread.Interval = 1000;

//Create and start a new thread. Thread DoThread2 =

new Thread(new ThreadStart(MyThread.CreateUIThread)); DoThread2.Start();

//Get rid of the variables.

DoThread2 = null; DoThread = null; MyThread = null;

}

When you run this code, you're using the same object for two threads. There's a possibility of the two threads creating a collision if they access the same variable at the same time. Here's a slightly modified version of the CreateUIThread() from Listing 7.4. Notice that it adds a Counter variable that makes collisions apparent.

public void CreateUIThread()

{

// Create a new form.

ThreadForm Dlg = new ThreadForm();

//Wait before assigning the dialog name. Thread.Sleep(_Interval);

//Name the dialog.

Dlg.Text = _ThreadDlgName + " " + Counter.ToString();

//Increment the counter. Counter++;

//Display the form. Dlg.ShowDialog();

}

Run this application without adding a critical section and you're likely to see the results of a collision. The two Thread Form dialog boxes will have the same number appended to the dialog box names. In some cases, you'll see the anticipated result; in others, you'll see that the

second thread actually has a 1 attached to its name and the first thread has a value of 2. That's because both threads access the CreateUIThread() in a haphazard manner. The fix for this problem is as follows:

public void CreateUIThread()

{

//Begin the critical section. Monitor.Enter(this);

//Create a new form.

ThreadForm Dlg = new ThreadForm();

//Wait before assigning the dialog name. Thread.Sleep(_Interval);

//Name the dialog.

Dlg.Text = _ThreadDlgName + " " + Counter.ToString();

//Increment the counter. Counter++;

//Exit the critical section. Monitor.Exit(this);

//Display the form. Dlg.ShowDialog();

}

Notice the addition of the Monitor.Enter(this) and Monitor.Exit(this) calls. These calls ensure that the first thread always finishes before the second thread gains access to the method. As a result, the first dialog always contains a 1 and the second dialog always contains a 2.

You should also note the placement of the critical section. If you place the Monitor.Exit(this) call after Dlg.ShowDialog(), the application will stop after the first dialog appears. It will wait for you to close the first dialog before it displays the second dialog. The point is that you need to place critical sections carefully or the performance hit on your system could be substantial.

Understanding Thread Safety

One of the benefits of using libraries is code reuse. Once a developer writes and tests the code, they can place it in a library and forget about it. The functionality you'll need will be available without a lot of additional work. All you need to do is access the DLL. Windows uses this technique for all of the APIs that it supports.

Unfortunately, the black box functionality of libraries can be a double-edged sword. One of the biggest problems when using libraries with threads is that the library isn't thread safe. In other words, if two threads attempt to access the same object or function at the same time, there could be a collision, resulting in a frozen application, lost data, or other unanticipated results.

Fortunately, you can protect your libraries in a number of ways. One way is to use critical sections as needed to ensure that a sequence of events takes place without interruption. A second way is to allocate variables and objects on the stack. Finally, it's extremely important

to reduce the risk of collisions by not allowing more than one thread to access the same variable—use techniques that ensure that each thread will have its own set of variables to use.

Even Microsoft's libraries aren't totally thread safe. For the most part, any object provided by the .NET Framework is thread safe at the class level but not at the object level. In other words, two threads could access different objects derived from the same class but not the same object. Fortunately, Microsoft has made thread safety a major issue in the help files that accompany Visual Studio .NET. Most of the help topics will tell you if the .NET Framework object you want to use is thread safe, so you'll want to verify thread safety each time you work with a new .NET Framework namespace or class.

However, this still doesn't guarantee thread safety for your code. You need to make your code thread safe as well. We discussed some of the techniques you can use to perform this task in the "Understanding Critical Sections" section of the chapter. Even if you don't plan to use a DLL you create today in a distributed application tomorrow, adding thread safety is insurance that the application you create tomorrow will work as anticipated.

Where Do You Go From Here?

Working with multiple threads can make your workstation more efficient and your server more available. However, misuse of threading techniques can easily run counter to these goals. You need to know when threads will do the job for you and when it's better to use a single-threaded approach. This chapter has provided a good understanding of how threads work and provided a good overview of when it's best to use them. You've also seen several threading examples.

Now it's time to work with threading examples of your own design. Look for places that might cause conflicts and add a critical section to them. Try various access scenarios to ensure that your applications, components, and services are thread safe.

Chapter 8 addresses Active Directory. You'll learn how to access this vast database of user, network, and machine information. We'll also look at some techniques for expanding the Active Directory schema as needed to meet customized needs.

Chapter 8: Working with Active Directory

Overview

Windows 2000 introduced a new centralized database of information called Active Directory. Essentially, Active Directory is a database manager that tracks the equipment and users on a network along with associated information such as security and policy settings. You can also expand the schema of the database to include custom information about your company and the applications that it owns. The changes made to the database on one server automatically appear on every other server on the system, making Active Directory a central storage location for all network information.

This chapter helps you understand Active Directory. We'll discuss the central database and the tools used to view and manipulate it. You'll also learn how to work with the existing database schema, as well as extend the schema for your needs. Finally, we'll discuss the features that

Windows and the .NET Framework make available for working with Active Directory. The interface you use determines the level of functionality that Active Directory provides.

What Is Active Directory?

Active Directory evokes a number of impressions by developers. Some developers see Active Directory as a super-sized Registry—an accurate but limited viewpoint. Each machine still has a Registry because Active Directory is global in nature, while the Registry handles local needs. A few developers even look at Active Directory as a direct competitor to other directory services. From a management perspective, Active Directory does serve this function, but from a developer perspective, the view is different. Many developers see Active Directory as something they'd rather not use because they perceive it as overly complex. Actually, Active Directory is robust and large, but it's based on a relatively simple idea.

The simple way to look at Active Directory is as a massive database designed to make network management easier for everyone. However, you may not realize just how massive this database is. As you'd expect, Active Directory holds the usual network information, such as user and group security setups. In addition, Active Directory helps you manage network resources like disk drives and printers. Finally, Active Directory contains a multitude of application settings—not local applications, but those that operate at an enterprise level.

In this section of the chapter, we'll look at a few of the things that you might not expect Active Directory to do. We'll also look at a few potential pitfalls you should consider when working with any network management system like Active Directory.

Note Active Directory requires a server. Windows XP isn't a server platform, it's used for the desktop alone and therefore doesn't provide Active Directory support. Because the name for Microsoft's new server product changes daily, I'll use the term Windows Server throughout the chapter to reference any Windows 2000 or newer server product.

An Overview of the Interface

Like any database application, Active Directory consists of three components: database engine, data storage, and user interface. In this section, we view the standard user interface for Active Directory; the Microsoft Management Console (MMC) and associated snap-ins. Microsoft put a lot of effort into creating a new management tool for Windows 2000 in the form of the MMC. Windows XP follows in this tradition, and there's no reason to expect Microsoft's latest server product to do otherwise. Consequently, any initial work with Active Directory will revolve around an MMC Console (the MMC application and one or more snapins). You need to use a few of the Active Directory–related consoles to work with the example in this chapter, so it's a good idea to review them now.

Note MMC is a container application for specialized components. If you get the idea that MMC is some form of COM technology, you'd be right. A snap-in is really nothing more than a component that uses MMC as a container.

Figure 8.1 shows a typical Active Directory Users and Computers console. Any predefined selection of MMC snap-ins is a console. You can also create custom snap-ins (the component) and consoles for your own use—something we'll discuss in Chapter 16. As you can see from the figure, the Active Directory Users and Computers console provides access to computer

and use resource information on your network. All of this information appears within Active Directory database entries on the server.

Figure 8.1: Microsoft provides Active Directory–related consoles that help you modify the Active Directory database.

Let's spend a few minutes talking about the various components of the display shown in Figure 8.1. At the top of the tree is the Active Directory root. Below this is the single domain in this tree, DataCon.domain. If there were multiple domains, then there would be multiple entries at this level of the tree. Don't confuse the domain controller with the domain as a whole. A domain can have more than one domain controller, and these controllers would appear in the Domain Controllers folder. The Builtin folder contains all of the built-in groups for the domain. The Computers folder holds a list of all the computers that have logged into the domain. Active Directory manages these first three folders, Builtin, Computers, and Domain Controllers automatically. Normally, you won't need to add new entries, but you'll have to configure the ones that Active Directory adds for you.

The last folder, Users, is misleading because it can contain a lot more than just users. At minimum, this folder can actually contain computers, contacts, groups, printers, users, and shared folders. An administrator can create other classes of objects to add into this folder, and you can design components to work with these classes. For the most part, you'll spend the greatest amount of time in this folder, unless you create additional folders of your own. Active Directory enables you to add new entries at every level of the database including the domain level. At the domain level, you can add computers, contacts, groups, organizational units, printers, users, and shared folders. However, unless you want a messy, hard-to-follow directory, you'll usually limit the entries at the domain level to organizational units.

You may have also noticed the Saved Queries folder in Figure 8.1. This is a new feature for the .NET Server version of the Active Directory Users and Computers snap-in. You can use this folder to create and save queries for later use. The benefit of this folder to the developer is that you can test queries before you place them in an application to ensure that they return the desired result. In addition, the Query String field of the Edit Query and Add Query dialog box shows the proper formatting of the query. You can copy the contents of this field and move them directly to the development environment.

Tip The workstation you use must have a domain connection to work with Active Directory.

One of the best ways to check whether your computer has logged into the domain and exchanged the proper information is to look in the Computers folder. The client machine will appear in this folder automatically after a successful logon and the client and server have exchanged information. This information exchange is essential for many kinds of COM-related activities.

Developers often need to check the status of their development machines. For example, you may want to ensure that the operating system is up to date. A developer can use Active Directory to check on the status of any accessible machine from a remote location. Open either the Computers or Domain Controllers folder, and then double-click on a computer icon. You'll see a Properties dialog that enables you to check machine statistics, such as the fully qualified domain name of the computer and the version of the operating system installed.

There are times when you need better access to the machine than the computer Properties dialog will provide. For example, you may need to know the hardware configuration of the machine or the status of the drivers. This information is also available from Active Directory. All you need to do is right-click the computer of interest and choose Manage from the context menu; you'll see the Computer Management console for that machine. The console groups them by System Tools (hardware), Storage (the content and organization of the hard drives), and Server Applications and Services (a list of services including COM+ and MSMQ).

By this time, you should have a better idea of why Active Directory, even the interface portion, is important for you as a programmer. Given the right tools, you can manage most testing scenarios and troubleshoot most application failures without even leaving your desk. In addition, Active Directory gives you access to real-world data, something that was difficult to collect in the past. Users tend to behave differently when you watch them directly. This difference in behavior affects the results you get when running tests and ultimately results in applications with less than perfect performance characteristics. While I'm not advocating a "big brother" approach to application testing, getting real-world data is an important part of working with today's complex application programming environment.

Why Use Active Directory?

Active Directory has a lot to offer the network administrator and developer alike. One of the most important considerations is that it provides complete information security. Not only will Active Directory allow you to set the security for individual objects, but you can set security on object properties as well. This level of functionality means that you can create an extremely detailed security policy that gives users access to what they need. In addition, you can block rights at the object or the property level, which means that giving someone access to an object no longer means that they necessarily get full access. Finally, you can delegate the authority to manage security on an object or even a property level.

Policy-based administration is another feature that Active Directory provides. Policies are an implementation of role-based security. Active Directory objects always have a context that defines how a particular user is using the object and expresses the user's rights to the object. All of this information is stored in the Active Directory database, making it easy for an Administrator to create policies that dictate the rights for entire groups of users. The combination of context, role-based security, and groups means that an administrator can manage security using a few groups of users, rather than mange individual users, and still be sure that individual users are getting the access they require.

As a developer, you're already well aware of the extensibility that Active Directory provides. However, you may not know that the administrator can extend Active Directory by adding new object classes or new attributes to existing classes. For example, you may want to add the number of sick leave and vacation days an employee has to their entry in Active Directory. A component that you build could keep this value updated so that the employee and manager could track this information without relying on another resource. Instead of a simple contact list, you might create special kinds of contacts; for instance, you could keep outside consultants who are paid by the company separate from large customers that the company relies on for income.

Scalability is another feature that makes Active Directory a good choice. Active Directory enables you to include multiple domains in a single database. Each domain could contain more than one domain controller. You can organize the entire setup into a contiguous namespace that Microsoft calls a directory tree. If your organization is so large that a single tree would be impossible to manage, you can combine directory trees into a non-contiguous namespace called a forest. The ability to scale a single Active Directory database over the largest organization means that when you search for a specific domain within your application, you'll find it —as long as you have a connection and the server is online.

As previously stated, DNS and Active Directory are coupled. What this means to you as a programmer is that the domain controller and other servers could use the same name no matter how they're accessed. A user who normally accesses a server from their desktop computer within the company wouldn't need to make any adjustment when accessing that same server using an Internet connection (assuming that you've properly registered your domain name). In addition, the components you create can access the server in the same way using any connection type.

Active Directory can use two standard directory access protocols for access purposes. The most common method is the Lightweight Directory Access Protocol (LDAP). You can find out more about this access method at http://www.faqs.org/rfcs/rfc2251.html. A secondary access method is Name Service Provider Interface (NSPI). This is a Microsoft standard used with Microsoft Exchange version 4.0 and above. Many third-party products work with Microsoft Exchange, so from a Microsoft-specific programming perspective this second access method is just as important as LDAP. However, you'll probably use LDAP when working with multiple directory types.

The last benefit of using Active Directory is the ability to query the database using any of a number of methods. From a user perspective, you can find any object on the network using Search, My Network Places, or Active Directory Users and Computers. We'll see in the next chapter that querying the database within your application is just as easy and flexible. Finding what you need isn't a problem with Active Directory.

Active Directory Programming Pitfalls

It would be frivolous to say that Active Directory will take care of every need you've ever had or ever will have. That just isn't realistic, despite what Microsoft's marketing arm would have you believe. A network management system like Active Directory can be a hindrance in more than a few ways. The following list provides you with a few ideas.

Domain versus Workgroup Active Directory assumes that the domain is everything and that workgroups, as such, really don't exist. Obviously, any company with more than a few employees will have workgroups, because in many situations this is the easiest way to work. Logging into the workgroup rather than the domain, though, can have unexpected results. For example, you can't set up services like MSMQ without a domain connection—at least not as an independent client.

Server Loading Moving from Windows NT to newer Windows Server versions can create performance problems. Unfortunately, many administrators will blame the new suite of components you've developed to take advantage of Windows Server features, despite the fact that the more likely culprit is the polling and data processing that Active Directory requires. All of that information takes processing cycles and system resources to collect.

Interface Complexity Microsoft's new MMC snap-ins may be one of the better ways to manage Active Directory, but the learning curve for this utility is astronomical and the complexity of Active Directory doesn't help. It seems as if there's a specialized snap-in for every situation. For the most part, you'll find that writing applications that take advantage of everything Active Directory has to offer greatly increases the administrative learning curve, unless you can work within the confines of the current interface.

Storage Active Directory stores everything you can imagine and probably a few things that you don't even know exist. As a result, disk storage needs for Windows Server have greatly increased over the same setup you had for Windows NT. This means you'll have to exercise care when expanding the database schema or face the consequences of large disk usage increases.

Programmer Learning Curve Active Directory relies on COM/COM+ components. Many developers are just learning COM and a few may be working with their first applications. The problem is that Active Directory uses some of Microsoft's most advanced technologies, making the learning curve for developers steep.

As you can see, there are many limitations when using Active Directory that you can categorize in one of two ways. Most of the limitations are due to either new resource requirements or the complexity of the Active Directory interface. It's important to keep these limitations in mind as you design projects that require Active Directory. The most important limitation now is the newness of the technology compared to other directory services on the market. For instance, Novell required several years after their initial release of NDS to make their product completely functional and make it at least moderately reliable.

Getting Support from Microsoft Newsgroups

Microsoft provides places where you can get help from peers and Microsoft support personnel. This help is free, unlike the help that you have to pay a support person to obtain, and is usually high in quality. If your ISP doesn't provide access to these newsgroups, you can access them using the news.microsoft.com news server. The Active Directory–related Microsoft newsgroups include:

microsoft.public.active.directory.interfaces

microsoft.public.exchange2000.active.directory.integration