1. Trang chủ
  2. » Công Nghệ Thông Tin

Professional C# 2008 phần 10 pptx

182 246 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 182
Dung lượng 2,36 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

As in the previous examples, the changes are written to the directory service by calling CommitChanges with the DirectoryEntry object: ActiveDs.IADsUser user = ActiveDs.IADsUserde.Native

Trang 1

Chapter 46: Directory Services

1605

Cache

To reduce the network transfers, ADSI uses a cache for the object properties As mentioned earlier, the server isn ’ t accessed when a DirectoryEntry object is created; instead, with the first reading of a value from the directory store, all the properties are written into the cache so that a round trip to the server isn ’ t necessary when the next property is accessed

Writing any changes to objects changes only the cached object; setting properties doesn ’ t generate network traffic You must use DirectoryEntry.CommitChanges() to flush the cache and to transfer any changed data to the server To get the newly written data from the directory store, you can use

DirectoryEntry.RefreshCache() to read the properties Of course, if you change some properties without calling CommitChanges() and do a RefreshCache() , all your changes will be lost, because you read the values from the directory service again using RefreshCache()

It is possible to turn off this property cache by setting the DirectoryEntry.UsePropertyCache property to false However, unless you are debugging your code, it ’ s better not to turn off the cache because of the extra round trips to the server that will be generated

Creating New Objects

When you want to create new Active Directory objects — such as users, computers, printers, contacts, and so on — you can do this programmatically with the DirectoryEntries class

To add new objects to the directory, first you have to bind to a container object, such as an organizational unit, where new objects can be inserted — you cannot use objects that are not able to contain other objects The following example uses the container object with the distinguished name CN=Users, DC=thinktecture, DC=local :

DirectoryEntry de = new DirectoryEntry();

de.Path = “LDAP://treslunas/CN=Users, DC=explorer, DC=local”;

You can get to the DirectoryEntries object with the Children property of a DirectoryEntry :

DirectoryEntries users = de.Children;

The class DirectoryEntries offers methods to add, remove, and find objects in the collection Here,

a new user object is created With the Add() method, the name of the object and a type name are required You can get to the type names directly using ADSI Edit

DirectoryEntry user = users.Add(“CN=John Doe”, “user”);

The object now has the default property values To assign specific property values, you can add properties with the Add() method of the Properties property Of course, all of the properties must exist in the schema for the user object If a specified property doesn ’ t exist, you ’ ll get a COMException:

“ The specified directory service attribute or value doesn ’ t exist “ :

Trang 2

Part VI: Communication

1606

Finally, to write the data to Active Directory, you must flush the cache:

user.CommitChanges();

Updating Directory Entries

Objects in the Active Directory service can be updated as easily as they can be read After reading the

object, you can change the values To remove all values of a single property, you can call the method

PropertyValueCollection.Clear() You can add new values to a property with Add() Remove()

and RemoveAt() remove specific values from a property collection

You can change a value simply by setting it to the specified value The following example uses an

indexer for PropertyValueCollection to set the mobile phone number to a new value With

the indexer a value can be changed only if it exists Therefore, you should always check

with DirectoryEntry.Properties.Contains() to see if the attribute is available:

using (DirectoryEntry de = new DirectoryEntry())

{

de.Path = “LDAP://treslunas/CN=Christian Nagel, “ +

“OU=thinktecture, DC=explorer, DC=local”;

The else part in this example uses the method PropertyValueCollection.Add() to add a new

property for the mobile phone number, if it doesn ’ t exist already If you use the Add() method with

already existing properties, the resulting effect would depend on the type of the property (single - value

or multivalue property) Using the Add() method with a single - value property that already exists results

in a COMException: “ A constraint violation occurred ” Using Add() with a multivalue

property, however, succeeds, and an additional value is added to the property

The mobile property for a user object is defined as a single - value property, so additional mobile phone

numbers cannot be added However, a user can have more than one mobile phone number For multiple

mobile phone numbers, the otherMobile property is available otherMobile is a multivalue property

that allows setting multiple phone numbers, and so calling Add() multiple times is allowed Note that

multivalue properties are checked for uniqueness If the second phone number is added to the same

user object again, you get a COMException: “ The specified directory service attribute or

value already exists ”

Remember to call DirectoryEntry.CommitChanges() after creating or updating

new directory objects Otherwise, only the cache gets updated, and the changes are

not sent to the directory service

Trang 3

Chapter 46: Directory Services

1607

Accessing Native ADSI Objects

Often, it is much easier to call methods of predefined ADSI interfaces instead of searching for the names

of object properties Some ADSI objects also support methods that cannot be used directly from the

DirectoryEntry class One example of a practical use is the IADsServiceOperations interface, which has methods to start and stop Windows services (For more details on Windows services see Chapter 23 , “ Windows Services ” )

The classes of the System.DirectoryServices namespace use the underlying ADSI COM objects as mentioned earlier The DirectoryEntry class supports calling methods of the underlying objects directly by using the Invoke() method

The first parameter of Invoke() requires the method name that should be called in the ADSI object; the

params keyword of the second parameter allows a flexible number of additional arguments that can be passed to the ADSI method:

public object Invoke(string methodName, params object[] args);

You can find the methods that can be called with the Invoke() method in the ADSI documentation

Every object in the domain supports the methods of the IADs interface The user object that you created previously also supports the methods of the IADsUser interface

In the following example, the method IADsUser.SetPassword() changes the password of the previously created user object:

using (DirectoryEntry de = new DirectoryEntry()){

de.Path = “LDAP://treslunas/CN=John Doe, “ + “CN=Users, DC=explorer, DC=local”;

de.Invoke(“SetPassword”, “anotherSecret”);

de.CommitChanges();

}

It is also possible to use the underlying ADSI object directly instead of using Invoke() To use these objects, choose Project Add Reference to add a reference to the Active DS Type Library (see Figure 46 - 9 ) This creates a wrapper class where you can access these objects in the namespace

ActiveDs

Figure 46 - 9

Trang 4

Part VI: Communication

1608

The native object can be accessed with the NativeObject property of the DirectoryEntry class

In the following example, the object de is a user object, so it can be cast to ActiveDs.IADsUser

SetPassword() is a method documented in the IADsUser interface, so you can call it directly instead

of using the Invoke() method By setting the AccountDisabled property of IADsUser to false ,

you can enable the account As in the previous examples, the changes are written to the directory service

by calling CommitChanges() with the DirectoryEntry object:

ActiveDs.IADsUser user = (ActiveDs.IADsUser)de.NativeObject;

user.SetPassword(“someSecret”);

user.AccountDisabled = false;

de.CommitChanges();

NET 3.5 reduces the need to invoke the native objects behind the NET class DirectoryEntry

.NET 3.5 gives you new classes to manage users in the namespace System.DirectoryServices

AccountManagement The classes from this namespace are explained later in this chapter

Searching in Active Directory

Because Active Directory is a data store optimized for read - mostly access, you will generally search for

values To search in Active Directory, the NET Framework provides the DirectorySearcher class

You can use DirectorySearcher only with the LDAP provider; it doesn ’ t work with the other

providers such as NDS or IIS

In the constructor of the DirectorySearcher class, you can define four important parts for the search

You can also use a default constructor and define the search options with properties

SearchRoot

The search root specifies where the search should start The default of SearchRoot is the root of the

domain you are currently using SearchRoot is specified with the Path of a DirectoryEntry object

Filter

The filter defines the values where you want to get hits The filter is a string that must be enclosed in

parentheses

Relational operators such as < = , = , and > = are allowed in expressions (objectClass=contact)

searches all objects of type contact ; (lastName > =Nagel) searches all objects alphabetically where the

lastName property is equal to or larger than Nagel

Expressions can be combined with the & and | prefix operators For example,

( (objectClass=user)(description=Auth*)) searches all objects of type user where the

property description starts with the string Auth Because the & and | operators are at the beginning of

the expressions, it is possible to combine more than two expressions with a single prefix operator

The default filter is (objectClass=*) so all objects are valid

The filter syntax is defined in RFC 2254, “ The String Representation of LDAP Search Filters ” You can

find this RFC at www.ietf.org/rfc/rfc2254.txt

PropertiesToLoad

With PropertiesToLoad , you can define a StringCollection of all the properties in which you are

interested Objects can have a lot of properties, most of which will not be important for your search

request You define the properties that should be loaded into the cache The default properties that are

returned if nothing is specified are the path and the name of the object

Trang 5

Chapter 46: Directory Services

SearchScope.Subtree defines that the search should go down the complete tree

The default value of the SearchScope property is SearchScope.Subtree

Search Limits

A search for specific objects in a directory service can span multiple domains To limit the search to the number

of objects or the time taken, you have some additional properties to define, as shown in the following table

Property Description

ClientTimeout The maximum time the client waits for the server to return a result If the

server does not respond, no records are returned

PageSize With a paged search , the server returns a number of objects defined with

the PageSize instead of the complete result This reduces the time for the client to get a first answer and the memory needed The server sends a cookie to the client, which is sent back to the server with the next search request so that the search can continue at the point where it finished

ServerPageTimeLimit For paged searches, this value defines the time a search should continue

to return a number of objects that are defined with the PageSize value If the time is reached before the PageSize value, the objects that were found up to that point are returned to the client The default value is – 1 , which means infinite

SizeLimit Defines the maximum number of objects that should be returned by the

search If you set the limit to a value larger than defined by the server (which is 1000), the server limit is used

ServerTimeLimit Defines the maximum time the server will search for objects When this time

is reached, all objects that are found up to this point are returned to the client The default is 120 seconds, and you cannot set the search to a higher value

ReferralChasing A search can cross multiple domains If the root that ’ s specified with

SearchRoot is a parent domain or no root was specified, the search can continue to child domains With this property, you can specify if the search should continue on different servers

ReferralChasingOption.None means that the search does not continue

on other servers

The value ReferralChasingOption.Subordinate specifies that the search should go on to child domains When the search starts at DC=Wrox, DC=com the server can return a result set and the referral to DC=France, DC=Wrox, DC=COM The client can continue the search in the subdomain

Trang 6

Part VI: Communication

With ReferralChasingOption.All , both external and subordinate referrals are returned

Tombstone If the property Tombstone is set to true , all deleted objects that match

the search are returned, too

VirtualListView If large results are expected with the search, the property

VirtualListView can be used to define a subset that should be returned from the search The subset is defined with the class DirectoryVirtualListView

In the search example, all user objects with a property description value of Author are searched in

the organizational unit thinktecture

First, bind to the organizational unit thinktecture This is where the search should

start Create a DirectorySearcher object where the SearchRoot is set The filter is defined as

( (objectClass=user)(description=Auth*)) , so that the search spans all objects of type user

with a description of Auth followed by something else The scope of the search should be a subtree so

that child organizational units within thinktecture are searched, too:

using (DirectoryEntry de =

new DirectoryEntry(“LDAP://OU=thinktecture, DC=explorer, DC=local”))

using (DirectorySearcher searcher = new DirectorySearcher())

You are ready to do the search However, the result should also be sorted DirectorySearcher has a

Sort property, where you can set a SortOption The first argument in the constructor of the

SortOption class defines the property that will be used for a sort; the second argument defines the

direction of the sort The SortDirection enumeration has Ascending and Descending values

To start the search, you can use the FindOne() method to find the first object, or FindAll()

FindOne() returns a simple SearchResult , whereas FindAll() returns a SearchResultCollection

Here, all authors should be returned, so FindAll() is used:

searcher.Sort = new SortOption(“givenName”, SortDirection.Ascending);

SearchResultCollection results = searcher.FindAll();

Trang 7

Chapter 46: Directory Services

1611

With a foreach loop, every SearchResult in the SearchResultCollection is accessed A

SearchResult represents a single object in the search cache The Properties property returns a

ResultPropertyCollection , where you access all properties and values with the property name and the indexer:

SearchResultCollection results = searcher.FindAll();

foreach (SearchResult result in results) {

ResultPropertyCollection props = result.Properties;

foreach (string propName in props.PropertyNames) {

Console.Write(“{0}: “, propName);

Console.WriteLine(props[propName][0]);

} Console.WriteLine();

}}

It is also possible to get the complete object after a search: SearchResult has a GetDirectoryEntry() method that returns the corresponding DirectoryEntry of the found object

The resulting output shows the beginning of the list of all thinktecture associates with the properties that have been chosen:

name: Christian Nagelwwwhomepage: http://www.christiannagel.comdescription: Author

givenname: Christianadspath: LDAP://treslunas/CN=Christian Nagel,OU=thinktecture,DC=explorer,DC=local

name: Christian Weyerdescription: Authorgivenname: Christianadspath: LDAP://treslunas/CN=Christian Weyer,OU=thinktecture,DC=explorer,DC=local

name: Ingo Rammerwwwhomepage: http://www.thinktecture.comdescription: Author

givenname: Ingoadspath: LDAP://treslunas/CN=Ingo Rammer,OU=thinktecture,DC=explorer,DC=local

Searching for User Objects

In this section, you build a Windows Forms application called UserSearch This application is flexible insofar as a specific domain controller, username, and password to access Active Directory can be entered; otherwise, the user of the running process is used In this application, you access the schema of the Active Directory service to get the properties of a user object The user can enter a filter string to search all user objects of a domain It ’ s also possible to set the properties of the user objects that should

be displayed

User Interface

The user interface shows numbered steps to indicate how to use the application (see Figure 46 - 10 ):

Trang 8

Part VI: Communication

1612

1 In the first step, Username , Password , and the Domain Controller can be entered All this

information is optional If no domain controller is entered, the connection works with serverless

binding If the username is missing, the security context of the current user is taken

2 A button allows all the property names of the user object to be loaded dynamically in the

listBoxProperties list box

3 After the property names are loaded, the properties to be displayed can be selected

The SelectionMode of the list box is set to MultiSimple

4 The filter to limit the search can be entered The default value set in this dialog box searches for

all user objects: (objectClass=user)

5 Now the search can start

Figure 46 - 10

Get the Schema Naming Context

This application has only two handler methods: one method for the button to load the properties and

one to start the search in the domain First, you read the properties of the user class dynamically from

the schema to display it in the user interface

In the handler buttonLoadProperties_Click() method, SetLogonInformation() reads the username,

password, and host name from the dialog box and stores them in members of the class Next, the method

SetNamingContext() sets the LDAP name of the schema and the LDAP name of the default context This

schema LDAP name is used in the call to set the properties in the list box: SetUserProperties()

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

{

try

{

SetLogonInformation();

Trang 9

Chapter 46: Directory Services

1613

SetNamingContext();

SetUserProperties(schemaNamingContext);

} catch (Exception ex) {

MessageBox.Show(“Check your inputs! “ + ex.Message);

}}protected void SetLogonInformation(){

username = (textBoxUsername.Text == “” ? null : textBoxUsername.Text);

password = (textBoxPassword.Text == “” ? null : textBoxPassword.Text);

hostname = textBoxHostname.Text;

if (hostname != “”) {

hostname += “/”;

}}

In the helper method SetNamingContext() , you are using the root of the directory tree to get the properties of the server You are interested in the value of only two properties: schemaNamingContext and defaultNamingContext

protected void SetNamingContext(){

using (DirectoryEntry de = new DirectoryEntry()) {

string path = “LDAP://” + hostname + “rootDSE”;

de.Username = username;

de.Password = password;

de.Path = path;

schemaNamingContext = de.Properties[“schemaNamingContext”][0].ToString();

defaultNamingContext = de.Properties[“defaultNamingContext”][0].ToString();

}}

Get the Property Names of the User Class

You have the LDAP name to access the schema You can use this to access the directory and read the properties You are interested in not only the properties of the user class, but also those of the base classes of user : Organizational - Person , Person , and Top In this program, the names of the base classes are hard - coded You could also read the base class dynamically with the subClassOf attribute

GetSchemaProperties() returns IEnumerable < string > with all property names of the specific object type All the property names are added to the list box:

protected void SetUserProperties(string schemaNamingContext){

var properties = from p in GetSchemaProperties(schemaNamingContext, “User”).Concat(

GetSchemaProperties(schemaNamingContext,

(continued)

Trang 10

Part VI: Communication

In GetSchemaProperties() , you are accessing the Active Directory service again This time, rootDSE

is not used but rather the LDAP name to the schema that you discovered earlier The property

systemMayContain holds a collection of all attributes that are allowed in the class objectType :

protected IEnumerable < string > GetSchemaProperties(string schemaNamingContext,

string objectType)

{

IEnumerable < string > data;

using (DirectoryEntry de = new DirectoryEntry())

PropertyValueCollection values = de.Properties[“systemMayContain”];

data = from s in values.Cast < string >

Search for User Objects

The handler for the search button calls only the helper method FillResult() :

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

Trang 11

Chapter 46: Directory Services

1615

In FillResult() , you do a normal search in the complete Active Directory Domain as you saw earlier

SearchScope is set to Subtree , the Filter to the string you get from a TextBox object, and the properties that should be loaded into the cache are set by the values the user selected in the list box

The PropertiesToLoad property of the DirectorySearcher is of type StringCollection where the properties that should be loaded can be added using the AddRange() method that requires a string array The properties that should be loaded are read from the ListBox listBoxProperties with the property SelectedItems After setting the properties of the DirectorySearcher object, the properties are searched by calling the SearchAll() method The result of the search inside the SearchResultCollection is used to generate summary information that is written to the text box textBoxResults :

protected void FillResult(){

using (DirectoryEntry root = new DirectoryEntry()) {

StringBuilder summary = new StringBuilder();

foreach (SearchResult result in results) {

foreach (string propName in result.Properties.PropertyNames) {

foreach (string s in result.Properties[propName]) {

summary.AppendFormat(“ {0}: {1}\r\n”, propName, s);

} } summary.Append(“\r\n”);

} textBoxResults.Text = summary.ToString();

} }}

Starting the application gives you a list of all objects where the filter is valid (see Figure 46 - 11 )

Trang 12

Part VI: Communication

1616

Account Management

Previous to NET 3.5, it was difficult to create and modify user and group accounts One way to do that

was by using the classes from the System.DirectoryServices namespace, or by using the strongly

typed native COM interfaces New with NET 3.5 is the assembly System.DirectoryServices

.AccountManagement that offers an abstraction to the System.DirectoryServices classes by

offering specific methods and properties to search, modify, create, and update users and groups

The classes and their functionality are explained in the following table

Class Description

PrincipalContext With the PrincipalContext , you configure the context of the

account management Here you can define if an Active Directory Domain, the accounts from the local system, or an application directory should be used You set this by setting the ContextType enumeration to one of the values Domain , Machine , or

ApplicationDirectory Depending on the context type, you can also define the name of the domain and specify a username and password that are used for access

Figure 46 - 11

Trang 13

Chapter 46: Directory Services

1617

Class Description

Principal Principal is the base class of all principals With the static

method FindByIdentity() , you can get a Principal identity object With a principal object, you have access to various proper-ties such as name, description, distinguished name, and also the object type from the schema In case you need more control about the principal than is available from the properties and methods of this class, the method GetUnderlyingType() returns the under-lying DirectoryEntry object

AuthenticablePrincipal AuthenticablePrincipal derives from Principal and is the

base class for all principals that can be authenticated There are several static methods to find principals, such as by logon or lock-out times, by incorrect password attempts, or by password set time Using instance methods, you can change the password and unlock an account

to get and set information about the user, for example,

EmployeeId , EmailAddress , GivenName ,

VoiceTelephoneNumber

GroupPrincipal Groups cannot authenticate; that ’ s why GroupPrincipal derives

directly from the Principal class With GroupPrincipal , you can get members of the group with the Members property and the

GetMembers() method

PrincipalCollection The PrincipalCollection contains a group of Principal

objects; for example, the Members property from the

GroupPrincipal class returns a PrincipalCollection object

PrincipalSearcher PrincipalSearcher is an abstraction of the

DirectorySearcher class with special use for account ment With PrincipalSearcher, there ’ s no need to know about the LDAP query syntax because this is created automatically

PrincipalSearchResult < > Search methods from the PrincipalSearcher and Principal

classes return a PrincipalSearchResult < >

The following sections look at some scenarios in which you can use the classes from the System.DirectoryServices.AccountManagement namespace

Trang 14

Part VI: Communication

1618

Display User Information

The static property Current of the UserPrincipal class returns a UserPrincipal object with

information about the currently logged - on user:

using (UserPrincipal user = UserPrincipal.Current)

Running the application displays information about the user:

Context Server: treslunas.explorer.local

You can use the UserPrincipal class to create a new user First a PrincipalContext is required to

define where the user should be created With the PrincipalContext , you set the ContextType to an

enumeration value of Domain , Machine , or ApplicationDirectory depending on whether the

directory service, the local accounts of the machine, or an application directory should be used If the

current user does not have access to add accounts to Active Directory, you can also set a user and

password with the PrincipalContext that is used to access the server

Next, you can create an instance of UserPrincipal passing the principal context, and setting all

required properties Here, the GivenName and EmailAddress properties are set Finally, you must

invoke the Save() method of the UserPrincipal to write the new user to the store:

using (PrincipalContext context =

new PrincipalContext(ContextType.Domain, “explorer”))

using (UserPrincipal user = new UserPrincipal(context, “Tom”,

Trang 15

Chapter 46: Directory Services

1619

using (PrincipalContext context = new PrincipalContext(ContextType.Domain, “explorer”)) using (UserPrincipal user = UserPrincipal.FindByIdentity(

context, IdentityType.Name, “Tom”)) {

A new group can be created in a similar way to creating a new user Here, just the class GroupPrincipal

is used instead of the class UserPrincipal As in creating a new user, the properties are set, and the

Save() method is invoked:

using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, “explorer”)) using (GroupPrincipal group = new GroupPrincipal(ctx) {

Description = “Sample group”, DisplayName = “Wrox Authors”, Name = “WroxAuthors”

}) { group.Save();

}

Add a User to a Group

To add a user to a group, you can use a GroupPrincipal and add a UserPrincipal to the Members property of the group To get an existing user and group, you can use the static method

FindByIdentity() :

using (PrincipalContext context = new PrincipalContext(ContextType.Domain)) using (GroupPrincipal group = GroupPrincipal.FindByIdentity(

context, IdentityType.Name, “WroxAuthors”)) using (UserPrincipal user = UserPrincipal.FindByIdentity(

context, IdentityType.Name, “Verena Oslzly”)) {

Trang 16

Part VI: Communication

1620

PrincipalSearchResult < UserPrincipal > collection that is iterated to display the

user name, the last logon time, and the time when the password was reset:

using (PrincipalContext context =

new PrincipalContext(ContextType.Domain, “explorer”))

using (PrincipalSearchResult < UserPrincipal > users =

Console.WriteLine(“{0}, last logon: {1}, “ +

“last password change: {2}”, user.Name, user.LastLogon,

user.LastPasswordSet);

}

}

Other methods offered by the UserPrincipal class to find users are FindByBadPasswordAttempt() ,

FindByExpirationTime() , FindByLockoutTime() , and FindByLogonTime()

You can get more flexibility in finding users by using the PrincipalSearcher class This class is an

abstraction of the DirectorySearcher class and uses this class behind the scenes With the

PrincipalSearcher class, you can assign any Principal object to the QueryFilter property In the

example here, a UserPrincipal object with the properties Surname and Enabled is set to the

QueryFilter This way, all user objects starting with the surname Nag and which are enabled are

returned with the PrincipalSearchResult collection The PrincipalSearcher class creates an LDAP

query string to do the search

PrincipalContext context = new PrincipalContext(ContextType.Domain);

With the namespace System.DirectoryServices.Protocols , you can access Active Directory

through DSML (Directory Services Markup Language) DSML is a standard defined by the OASIS group

( www.oasis-open.org ) that allows you to access directory services through a Web service

To make Active Directory available through DSML, you must have at least Windows Server 2003 R2 or

you must install DSML Services for Windows You can download DSML Services for Windows from the

Microsoft Web site: www.microsoft.com/windowsserver2003/downloads/featurepacks/

default.mspx

Trang 17

Chapter 46: Directory Services

Web serviceDSML

ActiveDirectory

DirectoryConnection DirectoryConnection is the base class of all the connection classes

that can be used to define the connection to the directory service The classes that derive from DirectoryConnection are LdapConnection (for using the LDAP protocol), DsmlSoapConnection , and

DsmlSoapHttpConnection With the method SendRequest , a message

is sent to the directory service

DirectoryRequest A request that can be sent to the directory service is defined by a class

that derives from the base class DirectoryRequest Depending on the request type, classes such as SearchRequest , AddRequest ,

DeleteRequest , and ModifyRequest can be used to send a request

DirectoryResponse The result that is returned with a SendRequest is of a type that derives

from the base class DirectoryResponse Examples for derived classes are SearchResponse , AddResponse , DeleteResponse , and

ModifyResponse

Searching for Active Directory Objects with DSML

This section looks at an example of how a search for directory services objects can be performed As you can see in the code that follows, first a DsmlSoapHttpConnection object is instantiated that defines the connection to the DSML service The connection is defined with the class DsmlDirectoryIdentifier that contains an Uri object Optionally, the user credentials can be set with the connection:

Uri uri = new Uri(“http://dsmlserver/dsml”);

DsmlDirectoryIdentifier identifier = new DsmlDirectoryIdentifier(uri);

NetworkCredential credentials = new NetworkCredential();

credentials.UserName = “cnagel”;

credentials.Password = “password”;

(continued)

Trang 18

Part VI: Communication

1622

credentials.Domain = “explorer”;

DsmlSoapHttpConnection dsmlConnection =

new DsmlSoapHttpConnection(identifier, credentials);

After the connection is defined, the search request can be configured The search request consists of

the directory entry where the search should start, an LDAP search filter, and the definition of what

property values should be returned from the search Here, the filter is set to (objectClass=user) ,

so that all user objects are returned from the search attributesToReturn is set to null , and you

can read all attributes that have values SearchScope is an enumeration in the namespace

System.DirectoryServices.Protocols that is similar to the SearchScope enumeration in the

namespace System.DirectoryServices used to define how deep the search should go Here,

the SearchScope is set to Subtree to walk through the complete Active Directory tree

The search filter can be defined with an LDAP string or by using an XML document contained in the

XmlDocument class:

string distinguishedName = null;

string ldapFilter = “(objectClass=user)”;

string[] attributesToReturn = null;// return all attributes

SearchRequest searchRequest = new SearchRequest(distinguishedName,

ldapFilter, SearchScope.Subtree, attributesToReturn);

After the search is defined with the SearchRequest object, the search is sent to the Web service by

calling the method SendRequest SendRequest is a method of the DsmlSoapHttpConnection class

SendRequest returns a SearchResponse object where the returned objects can be read

Instead of invoking the synchronous SendRequest method, the DsmlSoapHttpConnection class also

offers the asynchronous methods BeginSendRequest and EndSendRequest that conform to the

asynchronous NET pattern

The asynchronous pattern is explained in Chapter 19 , “ Threading and Synchronization ”

SearchResponse searchResponse =

(SearchResponse)dsmlConnection.SendRequest(searchRequest);

The returned Active Directory objects can be read within the SearchResponse SearchResponse

.Entries contains a collection of all entries that are wrapped with the type SearchResultEntry

The SearchResultEntry class has the Attributes property that contains all attributes Each attribute

can be read with help of the DirectoryAttribute class

In the code example, the distinguished name of each object is written to the console Next, the attribute

values for the organizational unit (OU) are accessed, and the name of the organizational unit is written

to the console After this, all values of the DirectoryAttribute objects are written to the console:

Console.WriteLine(“\r\nSearch matched {0} entries:”,

// retrieve a specific attribute

DirectoryAttribute attribute = entry.Attributes[“ou”];

Console.WriteLine(“{0} = {1}”, attribute.Name, attribute[0]);

// retrieve all attributes

(continued)

Trang 19

Chapter 46: Directory Services

{ Console.Write(“{0} “, value);

} Console.WriteLine();

} }

Adding, modifying, and deleting objects can be done similarly to searching objects Depending on the action you want to perform, you can use the corresponding classes

Summar y

This chapter discussed the architecture of Active Directory: the important concepts of domains, trees, and forests You can access information in the complete enterprise When writing applications that access Active Directory services, you must be aware that the data you read might not be up to date because of the replication latency

The classes in the System.DirectoryServices namespaces give you easy ways to access Active Directory services by wrapping to the ADSI providers The DirectoryEntry class makes it possible to read and write objects directly in the data store

With the DirectorySearcher class, you can perform complex searches and define filters, timeouts, properties to load, and a scope By using the global catalog, you can speed up the search for objects in the complete enterprise, because it stores a read - only version of all objects in the forest

DSML is another API that allows accessing the Active Directory through a Web service interface

Classes in System.DirectoryServices.AccountManagement offer an abstraction to make it easier to create and modify user, group, and computer accounts

The next chapter gives you another view on networking with peer - to - peer communication

Trang 21

Peer - to - Peer Networ king

Peer - to - peer networking, often referred to as P2P, is perhaps one of the most useful and yet misunderstood technologies to emerge in recent years When people think of P2P they usually think of one thing: sharing music files, often illegally This is because file - sharing applications such as BitTorrent have risen in popularity at a staggering rate, and these applications use P2P technology to work

However, though P2P is used in file - sharing applications, that isn ’ t to say that it doesn ’ t have other applications Indeed, as you see in this chapter, P2P can be used for a vast array of applications, and is becoming more and more important in the interconnected world in which we live You learn about this in the first part of this chapter, when you look at an overview of P2P technologies

Microsoft has not been oblivious to the emergence of P2P, and has been developing its own tools and technologies to use it You can use the Microsoft Windows Peer - to - Peer Networking platform

as a communication framework for P2P applications This platform includes the important components Peer Name Resolution Protocol (PNRP) and People Near Me (PNM) Also, version 3.5

of the NET Framework includes a new namespace, System.Net.PeerToPeer , and several new types and features that you can use to build P2P applications yourself with a minimum of effort

In this chapter you look at:

❑ An overview of P2P

❑ The Microsoft Windows Peer - to - Peer Networking platform, including PNRP and PNM

❑ How to build P2P applications with the NET Framework

❑ An example P2P application built using the NET Framework

Peer - to - Peer Networ king Over view

Peer - to - peer networking is an alternative approach to network communication In order to understand how P2P differs from the “ standard ” approach to network communication it is necessary to take a step backward and look at client - server communications Client - server communications are ubiquitous in networked applications today

Trang 22

Part VI: Communication

1626

Client - Server Architecture

Traditionally, you interact with applications over a network (including the Internet) using a client - server

architecture Web sites are a great example of this When you look at a Web site you send a request over

the Internet to a Web server, which then returns the information that you require If you want to

download a file, you do so directly from the Web server

Similarly, desktop applications that include local or wide area network connectivity will typically

connect to a single server, for example, a database server or a server that hosts other services

This simple form of client - server architecture is illustrated in Figure 47 - 1

ClientsServer

Figure 47-2

Server

ClientRequest Response

Figure 47-1

There is nothing inherently wrong with the client - server architecture, and indeed in many cases it will be

exactly what you want However, there is a scalability problem Figure 47 - 2 shows how the client - server

architecture scales with additional clients

With every client that is added an increased load is placed on the server, which must communicate with

each client To return to the Web site example, this is how Web sites collapse When there is too much

traffic the server simply becomes unresponsive

There are of course scaling options that you can implement to mitigate this situation You can scale up by

increasing the power and resources available to the server, or you can scale out by adding additional

servers Scaling up is of course limited by the technology available and the cost of better hardware

Scaling out is potentially more flexible, but requires an additional infrastructure layer to ensure that

clients either communicate with individual servers or that clients can maintain session state independent

of the server with which they are communicating Plenty of solutions are available for this, such as Web

or server farm products

Trang 23

Chapter 47: Peer-to-Peer Networking

P2P Architecture

The peer - to - peer approach is completely different from either the scaling up or scaling out approach

With P2P, instead of focusing on and attempting to streamline the communication between the server and its clients, you instead look at ways in which clients can communicate with each other

Say, for example, that the Web site that clients are communicating with is wrox.com In our imaginary scenario, Wrox has announced that a new version of this book is to be released on the wrox.com web site and will be free to download to anyone who wants it, but that it will be removed after one day Before the book is available on the Web site you might imagine that an awful lot of people will be looking at the Web site and refreshing their browsers, waiting for the file to appear Once the file is available, everyone will try to download it at the same time, and more than likely the wrox.com Web server will collapse under the strain You could use P2P technology to prevent this Web server collapse from occurring Instead of sending the file directly from the server to all the clients, you send the file to just a few clients A few of the remaining clients then download the file from the clients that already have it, a few more clients download it from those second - level clients, and so on In fact, this process is made even faster by splitting the file into chunks and dividing these chunks between clients, some of whom download it directly from the server, and some of whom download chunks from other clients This is how file - sharing technologies such as BitTorrent work, and is illustrated in Figure 47 - 3

Trang 24

Part VI: Communication

1628

Every client participating in a P2P network application must be able to perform the following operations

to overcome these problems:

It must be able to discover other clients

It must be able to connect to other clients

It must be able to communicate with other clients

The discovery problem has two obvious solutions You can either keep a list of the clients on the server

so clients can obtain this list and contact other clients (known as peers ), or you can use an infrastructure

(for example PNRP, covered in the next section) that enables clients to find each other directly Most file

sharing systems use the “ list on a server ” solution, by using servers know as trackers Also, in file

sharing systems any client may act as a server as shown in Figure 47 - 3 , by declaring that it has a file

available and registering it with a tracker In fact, a pure P2P network needs no servers at all, just peers

The connection problem is a more subtle one, and concerns the overall structure of the networks used by

a P2P application If you have one group of clients, all of which can communicate with one another, the

topology of the connections between these clients can become extremely complex You can often improve

performance by having more than one group of clients, each of which consists of connections between

clients in that group, but not to clients in other groups If you can make these groups locale - based you

will get an additional performance boost, because clients can communicate with each other with fewer

hops between networked computers

Communication is perhaps a problem of lesser importance, because communication protocols such as

TCP/IP are well established and can be reused here There is, however, scope for improvement in both

high - level technologies (for example, you can use WCF services and therefore all the functionality that

WCF offers) and low - level protocols (such as multicast protocols to send data to multiple endpoints

simultaneously)

Discovery, connection, and communication are central to any P2P implementation The implementation

you look at in this chapter is to use the System.Net.PeerToPeer types with PNM for discovery and

PNRP for connection As you see in subsequent sections, these technologies cover all three of these

operations

P2P Terminology

In the previous sections you were introduced to the concept of a peer , which is how clients are referred to

in a P2P network The word “ client ” makes no sense in a P2P network because there is not necessarily a

server to be a client of

Groups of peers that are connected to each other are known by the interchangeable terms meshes , clouds ,

or graphs A given group can be said to be well - connected if:

❑ There is a connection path between every pair of peers, so that every peer can connect to any

other peer as required

❑ There are a relatively small number of connections to traverse between any pair of peers

❑ Removing a peer will not prevent other peers from connecting to each other

Note that this does not mean that every peer must be able to connect to every other peer In fact, if you

analyze a network mathematically you will find that peers need to connect only to a relatively small

number of other peers in order for these conditions to be met

Another P2P concept to be aware of is that of flooding Flooding is the way in which a single piece of data

may be propagated through a network to all peers, or of querying other nodes in a network to locate a

specific piece of data In unstructured P2P networks this is a fairly random process of contacting nearest

Trang 25

Chapter 47: Peer-to-Peer Networking

neighbor peers, which in turn contact their nearest neighbors, and so on until every peer in the network

is contacted It is also possible to create structured P2P networks such that there are well - defined pathways for queries and data flow among peers

P2P Solutions

Once you have an infrastructure for P2P you can start to develop not just improved versions of client - server applications, but entirely new applications P2P is particularly suited to the following classes of applications:

❑ Content distribution applications, including the file - sharing applications discussed earlier

❑ Collaboration applications, such as desktop sharing and shared whiteboard applications

❑ Multi - user communication applications that allow users to communicate and exchange data directly rather than through a server

❑ Distributed processing applications, as an alternative to supercomputing applications that process enormous amounts of data

❑ Web 2.0 applications that combine some or all of the above in dynamic next - generation Web applications

Microsoft Windows Peer - to - Peer Networ king

The Microsoft Windows Peer - to - Peer Networking platform is Microsoft ’ s implementation of P2P technology It is part of Windows XP SP2 and Windows Vista, and is also available as an add - on for Windows XP SP1 It includes two technologies that you can use when creating NET P2P applications:

❑ The Peer Name Resolution Protocol (PNRP), which is used to publish and resolve peer addresses

❑ The People Near Me server, which is used to locate local peers (currently Vista only)

In this section you learn about both of these

Peer Name Resolution Protocol ( PNRP )

You can of course use any protocol at your disposal to implement a P2P application, but if you are working in a Microsoft Windows environment (and, let ’ s face it, if you ’ re reading this book you probably are) it makes sense to at least consider PNRP There have been two versions of PNRP released to date

PNRP version 1 was included in Windows XP SP2, Windows XP Professional x64 Edition, and Windows

XP SP1 with the Advanced Networking Pack for Windows XP PNRP version 2 was released with Windows Vista, and was made available to Windows XP SP2 users through a separate download (see KB920342 at support.microsoft.com/kb/920342 ) Version 1 and version 2 of PNRP are not compatible, and this chapter covers only version 2

In itself, PNRP doesn ’ t give you everything you need to create a P2P application Rather, it is one of the underlying technologies that you use to resolve peer addresses PNRP enables a client to register an

endpoint (known as a peer name ) that is automatically circulated among peers in a cloud This peer name

is encapsulated in a PNRP ID A peer that discovers the PNRP ID is able to use PNRP to resolve it to the

actual peer name, and can then communicate directly with it

For example, you might define a peer name that represents a WCF service endpoint You could use PNRP to register this peer name in a cloud as a PNRP ID A peer running a suitable client application that uses a discovery mechanism that can identify peer names for the service you are exposing might

Trang 26

Part VI: Communication

1630

then discover this PNRP ID Once discovered, the peer would use PNRP to locate the endpoint of the

WCF service and then use that service

An important point is that PNRP makes no assumptions about what a peer name actually represents It

is up to peers to decide how to use them once discovered The information a peer receives from PNRP

when resolving a PNRP ID includes the IPv6 (and usually also the IPv4) address of the publisher of the

ID, along with a port number and optionally a small amount of additional data Unless the peer knows

what the peer name means it is unlikely to be able to do anything useful with this information

PNRP IDs

PNRP IDs are 256 - bit identifiers The low - order 128 bits are used to uniquely identify a particular peer,

and the high - order 128 bits identify a peer name The high - order 128 bits are a hashed combination of a

hashed public key from the publishing peer and a string of up to 149 characters that identifies the peer

name The hashed public key (known as the authority ) combined with this string (the classifier ) are together

referred to as the P2P ID It is also possible to use a value of 0 instead of a hashed public key, in which case

the peer name is said to be unsecured (as opposed to secured peer names, which use a public key)

The structure of a PNRP ID is illustrated in Figure 47 - 4

The PNRP service on a peer is responsible for maintaining a list of PNRP IDs, including the ones that it

publishes as well as a cached list of those it has obtained by PNRP service instances elsewhere in

the cloud When a peer attempts to resolve a PNRP ID, the PNRP service either uses a cached copy of the

endpoint to resolve the peer that published the PNRP or it asks its neighbors if they can resolve it

Eventually a connection to the publishing peer is made and the PNRP service can resolve the PNRP ID

Note that all of this happens without you having to intervene in any way All you have to do is ensure that

peers know what to do with peer names once they have resolved them using their local PNRP service

Peers can use PNRP to locate PNRP IDs that match a particular P2P ID You can use this to implement a

very basic form of discovery for unsecured peer names This is because if several peers expose an

unsecured peer name that uses the same classifier, the P2P ID will be the same Of course, because any

peer can use an unsecured peer name you have no guarantee that the endpoint you connect to will be

the sort of endpoint you expect, so this is only really a viable solution for discovery over a local network

PNRP Clouds

In the preceding discussion you learned how PNRP registers and resolves peer names in clouds A cloud

is maintained by a seed server , which can be any server running the PNRP service that maintains a record

of at least one peer Two types of cloud are available to the PNRP service:

Trang 27

Chapter 47: Peer-to-Peer Networking

Link local — These clouds consist of the computers attached to a local network A PC may be

connected to more than one link local cloud if it has multiple network adapters

Global — This cloud consists of computers connected to the Internet by default, although it is

also possible to define a private global cloud The difference is that Microsoft maintains the seed server for the global Internet cloud, whereas if you define a private global cloud you must use your own If you use your own seed server you must ensure that all peers connect to it by configuring policy settings

In past releases of PNRP there was a third type of cloud, site local This is no longer used and you won ’ t look at it in this chapter

You can discover what clouds you are connected to with the following command:

netsh p2p pnrp cloud show list

A typical result is shown in Figure 47 - 5

Figure 47-5

Figure 47 - 5 shows that a single cloud is available, and that it is a link local cloud You can tell this from both the name and the Scope value, which is 3 for link local clouds and 1 for global clouds In order to connect to a global cloud you must have a global IPv6 address The computer used to generate Figure

47 - 5 does not have one, which is why only a local cloud is available

Clouds may be in one of the following states:

Active — If the state of a cloud is active, you can use it to publish and resolve peer names

Alone — If the peer you are querying the cloud from is not connected to any other peers, it will

have a state of alone

No Net — If the peer is not connected to a network, the cloud state may change from active to

no net

Synchronizing — Clouds will be in the synchronizing state when the peer is connecting to

them This state will change to another state extremely quickly because this connection does not take long, so you will probably never see a cloud in this state

Virtual — The PNRP service connects to clouds only as required by peer name registration and

resolution If a cloud connection has been inactive for more than 15 minutes it may enter the virtual state

If you experience network connectivity problems you should check your firewall in case it is preventing local network traffic over the UDP ports 3540 or 1900 UDP port 3540 is used by PNRP, and UDP port

1900 is used by the Simple Service Discovery Protocol (SSDP), which in turn is used by the PNRP service (as well as UPnP devices)

Trang 28

Part VI: Communication

1632

People Near Me

PNRP, as you saw in the previous section, is used to locate peers This is obviously important as an

enabling technology when you consider the discovery/connection/communication process of a P2P

application, but in itself is not a complete implementation of any of these stages The People Near Me

service is an implementation of the discovery stage, and enables you to locate peers that are signed into

the Window People Near Me service in your local area (that is, in a link local cloud that you are

connected to)

You may have come across this service because it is built into Vista, and is used in the Windows Meeting

Space application, which you can use for sharing applications among peers You can configure this

service through the Start menu with the dialog shown in Figure 47 - 6

Figure 47-6

Once signed in the service is available to any application that is built to use the PNM service

At the time of writing, PNM is available only on the Windows Vista family of operating systems

However, it is possible that future service packs or additional downloads may make it available on

Windows XP

Building P2P Applications

Now that you have learned what P2P networking is and what technologies are available to NET

developers to implement P2P applications, it is time to look at how you can build them From the

previous discussion you know that you will be using PNRP to publish, distribute, and resolve peer

names, so the first thing you look at here is how to achieve that using NET Next you look at how to use

PNM as a framework for a P2P application This can be advantageous because you do not have to

implement your own discovery mechanisms

Trang 29

Chapter 47: Peer-to-Peer Networking

To examine these subjects you need to learn about the classes in the following namespaces:

❑ System.Net.PeerToPeer

❑ System.Net.PeerToPeer.Collaboration

To use these classes you must have a reference to the System.Net.dll assembly

System.Net.PeerToPeer

The classes in the System.Net.PeerToPeer namespace encapsulate the API for PNRP and enable you

to interact with the PNRP service You will use these classes for two main tasks:

❑ Registering peer names

❑ Resolving peer names

In the following sections, all the types referred to come from the System.Net.PeerToPeer namespace unless otherwise specified

Registering Peer Names

To register a peer name you must carry out the following steps:

1 Create a secured or unsecured peer name with a specified classifier

2 Configure a registration for the peer name, specifying some or none of the following optional information:

❑ A TCP port number

❑ The cloud or clouds to register the peer name with (if unspecified, PNRP will register the peer name in all available clouds)

❑ A comment of up to 39 characters

❑ Up to 4096 bytes of additional data

❑ Whether to generate endpoints for the peer name automatically (the default behavior, where endpoints will be generated from the IP address or addresses of the peer and, if specified, the port number)

❑ A collection of endpoints

3 Use the peer name registration to register the peer name with the local PNRP service

After Step 3 the peer name will be available to all peers in the selected cloud (or clouds) Peer registration continues until it is explicitly stopped, or until the process that registered the peer name is terminated

To create a peer name you use the PeerName class You create an instance of this class from a string representation of a P2P ID in the form authority.classifier or from a classifier string and a

PeerNameType You can use PeerNameType.Secured or PeerNameType.Unsecured For example:

PeerName pn = new PeerName(“Peer classifier”, PeerNameType.Secured);

Because an unsecured peer name uses an authority value of 0, the following lines of code are equivalent:

PeerName pn = new PeerName(“Peer classifier”, PeerNameType.Unsecured);

PeerName pn = new PeerName(“0.Peer classifier”);

Trang 30

Part VI: Communication

1634

Once you have a PeerName instance you can use it along with a port number to initialize a

PeerNameRegistration object:

PeerNameRegistration pnr = new PeerNameRegistration(pn, 8080);

Alternatively, you can set the PeerName and (optionally) the Port properties on a

PeerNameRegistration object created using its default parameter You can also specify a Cloud

instance as a third parameter of the PeerNameRegistration constructor, or through the Cloud

property You can obtain a Cloud instance from the cloud name or by using one of the following static

members of Cloud :

❑ Cloud.Global — This static property obtains a reference to the global cloud This may be a

private global cloud depending on peer policy configuration

❑ Cloud.AllLinkLocal — This static field gets a cloud that contains all the link local clouds

available to the peer

❑ Cloud.Available — This static field gets a cloud that contains all the clouds that are available

to the peer, which includes link local clouds and (if available) the global cloud

Once created, you can set the Comment and Data properties if you want to Be aware of the limitations of

these properties, though You will receive a PeerToPeerException if you try to set Comment to a string

of greater than 39 Unicode characters or an ArgumentOutOfRangeException if you try to set Data to a

byte[] of greater than 4096 bytes You can also add endpoints by using the EndPointCollection

property This property is a System.Net.IPEndPointCollection collection of System.Net

IPEndPoint objects If you use the EndPointCollection property you might also want to set the

UseAutoEndPointSelection property to false to prevent automatic generation of endpoints

When you are ready to register the peer name you can call the PeerNameRegistration.Start()

method To remove a peer name registration from the PNRP service you use the

PeerNameRegistration.Stop() method

The following code registers a secured peer name with a comment:

PeerName pn = new PeerName(“Peer classifier”, PeerNameType.Unsecured);

PeerNameRegistration pnr = new PeerNameRegistration(pn, 8080);

pnr.Comment = “Get pizza here”;

pnr.Start();

Resolving Peer Names

To resolve a peer name you must carry out the following steps:

1 Generate a peer name from a known P2P ID or a P2P ID obtained through a discovery

technique

2 Use a resolver to resolve the peer name and obtain a collection of peer name records You can

limit the resolver to a particular cloud and/or a maximum number of results to return

3 For any peer name records that you obtain, obtain peer name, endpoint, comment, and

additional data information as required

This process starts with a PeerName object just like peer name registration The difference here is that

you use a peer name that is registered by one or more remote peers The simplest way to get a list of

active peers in your link local cloud is for each peer to register an unsecured peer name with the same

classifier and to use the same peer name in the resolving phase However, this is not a recommended

strategy for global clouds because unsecured peer names are easily spoofed

To resolve peer names you use the PeerNameResolver class Once you have an instance of this class

you can choose to resolve peer names synchronously by using the Resolve() method, or

asynchronously using the ResolveAsync() method

Trang 31

Chapter 47: Peer-to-Peer Networking

You can call the Resolve() method with a single PeerName parameter, but you can also pass an optional Cloud instance to resolve in, an int maximum number of peers to return, or both This method returns a PeerNameRecordCollection instance, which is a collection of PeerNameRecord objects For example, the following code resolves an unsecured peer name in all link local clouds and returns a maximum of 5 results:

PeerName pn = new PeerName(“0.Peer classifier”);

PeerNameResolver pnres = new PeerNameResolver();

PeerNameRecordCollection pnrc = pnres.Resolve(pn, Cloud.AllLinkLocal, 5);

The ResolveAsync() method uses a standard asynchronous method call pattern You pass a unique

userState object to the method, and listen for ResolveProgressChanged events for peers being found and the ResolveCompleted event when the method terminates You can cancel a pending asynchronous request with the ResolveAsyncCancel() method

Event handlers for the ResolveProgressChanged event use the ResolveProgressChangedEventArgs event arguments parameter, which derives from the standard System.ComponentModel

.ProgressChangedEventArgs class You can use the PeerNameRecord property of the event argument object you receive in the event handler to get a reference to the peer name record that was found

Similarly, the ResolveCompleted event requires an event handler that uses a parameter of type

ResolveCompletedEventArgs , which derives from AsyncCompletedEventArgs This type includes a

PeerNameRecordCollection parameter you can use to obtain a complete list of the peer name records that were found

The following code shows an implementation of event handlers for these events:

private pnres_ResolveProgressChanged(object sender, ResolveProgressChangedEventArgs e)

{ // Use e.ProgressPercentage (inherited from base event args) // Process PeerNameRecord from e.PeerNameRecord

} private pnres_ResolveCompleted(object sender, ResolveCompletedEventArgs e)

{ // Test for e.IsCancelled and e.Error (inherited from base event args) // Process PeerNameRecordCollection from e.PeerNameRecordCollection}

Once you have one or more PeerNameRecord objects you can proceed to process them This

PeerNameRecord class exposes Comment and Data properties to examine the comment and data set in the peer name registration (if any), a PeerName property to get the PeerName object for the peer name record, and, most importantly, an EndPointCollection property As with PeerNameRegistration , this property is a System.Net.IPEndPointCollection collection of System.Net.IPEndPoint objects You can use these objects to connect to end points exposed by the peer in any way you want

Code Access Security in System.Net.PeerToPeer

The System.Net.PeerToPeer namespace also includes the following two classes that you can use with CAS (see Chapter 20 ):

❑ PnrpPermission , which inherits from CodeAccessPermission

❑ PnrpPermissionAttribute , which inherits from CodeAccessSecurityAttribute You can use these classes to provide permissions functionality for PNRP access in the usual CAS way

Trang 32

Part VI: Communication

1636

Sample Application

The downloadable code for this chapter includes a sample P2P application (P2PSample) that uses the

concepts and namespace introduced in this section It is a WPF application that uses a WCF service for a

peer endpoint

The application is configured with an application configuration file, in which you can specify the name

of the peer and a port to listen on as follows:

< ?xml version=”1.0” encoding=”utf-8” ? >

< configuration >

< appSettings >

< add key=”username” value=”Karli” / >

< add key=”port” value=”8731” / >

< /appSettings >

< /configuration >

Once you have built the application you can test it either by copying it to other computers in your local

network and running all instances, or by running multiple instances on one computer If you choose the

latter option you must remember to change the port used for each instance by changing individual

config files (copy the contents of the Debug directory on your local computer and edit each config file in

turn) Things will be clearer in both ways of testing this application if you also change the username for

each instance

Once the peer applications are running, you can use the Refresh button to obtain a list of peers

asynchronously When you have located a peer you can send a default message by clicking the Message

button for the peer

Figure 47 - 7 shows this application in action with three instances running on one machine In the figure,

one peer has just messaged another and this has resulted in a dialog box

Figure 47-7

Most of the work in this application takes place in the Window_Loaded() event handler for the Window1

window This method starts by loading configuration information and setting the window title with the

username:

private void Window_Loaded(object sender, RoutedEventArgs e)

{

// Get configuration from app.config

string port = ConfigurationManager.AppSettings[“port”];

Trang 33

Chapter 47: Peer-to-Peer Networking

string username = ConfigurationManager.AppSettings[“username”];

string machineName = Environment.MachineName;

string serviceUrl = null;

// Set window title this.Title = string.Format(“P2P example - {0}”, username);

Next the peer host address is used along with the configured port to determine the endpoint on which to host the WCF service The service will use NetTcpBinding binding, so the URL of the endpoint uses the

net.tcp protocol:

// Get service url using IPv4 address and port from config file foreach (IPAddress address in Dns.GetHostAddresses(Dns.GetHostName())) {

if (address.AddressFamily ==

System.Net.Sockets.AddressFamily.InterNetwork) {

serviceUrl = string.Format(“net.tcp://{0}:{1}/P2PService”, address, port);

break;

} }

The endpoint URL is validated, and then the WCF service is registered and started:

// Check for null address

if (serviceUrl == null) {

// Display error and shutdown MessageBox.Show(this, “Unable to determine WCF endpoint.”, “Networking Error”, MessageBoxButton.OK, MessageBoxImage.Stop);

Application.Current.Shutdown();

} // Register and start WCF service

localService = new P2PService(this, username);

host = new ServiceHost(localService, new Uri(serviceUrl));

NetTcpBinding binding = new NetTcpBinding();

binding.Security.Mode = SecurityMode.None;

host.AddServiceEndpoint(typeof(IP2PService), binding, serviceUrl);

try { host.Open();

} catch (AddressAlreadyInUseException) {

// Display error and shutdown MessageBox.Show(this, “Cannot start listening, port in use.”, “WCF Error”, MessageBoxButton.OK, MessageBoxImage.Stop);

Application.Current.Shutdown();

}

A singleton instance of the service class is used to enable easy communication between the host app and the service (for sending and receiving messages) Also, note that security is disabled in the binding configuration for simplicity

Trang 34

Part VI: Communication

1638

Next, the System.Net.PeerToPeer namespace classes are used to register a peer name:

// Create peer name

peerName = new PeerName(“P2P Sample”, PeerNameType.Unsecured);

// Prepare peer name registration in link local clouds

peerNameRegistration = new PeerNameRegistration(peerName, int.Parse(port));

When the Refresh button is clicked the RefreshButton_Click() event handler uses

PeerNameResolver.ResolveAsync() to get peers asynchronously:

private void RefreshButton_Click(object sender, RoutedEventArgs e)

{

// Create resolver and add event handlers

PeerNameResolver resolver = new PeerNameResolver();

// Resolve unsecured peers asynchronously

resolver.ResolveAsync(new PeerName(“0.P2P Sample”), 1);

}

The remainder of the code is responsible for displaying and communicating with peers, and you can

explore it at your leisure

Exposing WCF endpoints through P2P clouds is a great way of locating services within an enterprise, as

well as being an excellent way to communicate between peers as in this example

System.Net.PeerToPeer.Collaboration

The classes in the System.Net.PeerToPeer.Collaboration namespace provide a framework you can

use to create applications that use the People Near Me service and the P2P collaboration API As

mentioned earlier, this is possible only if you are using Windows Vista at the moment

You can use the classes in this namespace to interact with peers and applications in a number of

ways, including:

❑ Signing in and signing out

❑ Discovering peers

❑ Managing contacts and detecting peer presence

You can also use the classes in this namespace to invite other users to join an application, and to

exchange data between users and applications However, in order to do this you need to create your own

PNM - capable applications, which is beyond the scope of this chapter

Trang 35

Chapter 47: Peer-to-Peer Networking

In the following sections, all the types referred to come from the System.Net.PeerToPeer

Collaboration namespace unless otherwise specified

Signing In and Signing Out

One of the most important classes in the System.Net.PeerToPeer.Collaboration namespace is the

PeerCollaboration class This is a static class that exposes numerous static methods that you can use for various purposes, as you will see in this and subsequent sections You can use two of the methods it exposes, SignIn() and SignOut() , to (unsurprisingly) sign in and sign out of the People Near Me service Both of these methods take a single parameter of type PeerScope , which can be one of the following values:

❑ PeerScope.None — If you use this value, SignIn() and SignOut() will have no effect

❑ PeerScope.NearMe — This will sign you in to or out of the link local clouds

❑ PeerScope.Internet — This will sign you in to or out of the global cloud (which may be necessary to connect to a contact who is not currently on your local subnet)

❑ PeerScope.All — This will sign you in to or out of all available clouds

If necessary, calling SignIn() will cause the People Near Me configuration dialog to be displayed

When a peer is signed in you can use the PeerCollaboration.LocalPresenceInfo property to a value of type PeerPresenceInfo This enables standard IM functionality, such as setting your status to away You can set the PeerPresenceInfo.DescriptiveText property to a Unicode string of up to 255 characters, and the PeerPresenceInfo.PresenceStatus property to a value from the

PeerPresenceStatus enumeration The values that you can use for this enumeration are as follows:

❑ PeerPresenceStatus.Away — The peer is away

❑ PeerPresenceStatus.BeRightBack — The peer is away, but will be back soon

❑ PeerPresenceStatus.Busy — The peer is busy

❑ PeerPresenceStatus.Idle — The peer isn ’ t active

❑ PeerPresenceStatus.Offline — The peer is offline

❑ PeerPresenceStatus.Online — The peer is online and available

❑ PeerPresenceStatus.OnThePhone — The peer is busy with a phone call

❑ PeerPresenceStatus.OutToLunch — The peer is away, but will be back after lunch

Discovering Peers

You can obtain a list of peers near you if you are logged in to the link local cloud You do this by using the PeerCollaboration.GetPeersNearMe() method This returns a PeerNearMeCollection object containing PeerNearMe objects

You can use the Nickname property of PeerNearMe to obtain the name of a peer, IsOnline to determine whether the peer is online, and (for lower - level operations) the PeerEndpoints property to determine endpoints related to the peer PeerEndPoints is also necessary if you want to find out the online status

of a PeerNearMe You can pass an endpoint to the GetPresenceInfo() method to obtain a

PeerPresenceInfo object, as described in the previous section

Managing Contacts and Detecting Peer Presence

Contacts are a way in which you can remember peers You can add a peer discovered through the People Near Me service and from then onward you can connect to them whenever you are both online You can

Trang 36

Part VI: Communication

1640

connect to a contact through link local or global clouds (assuming you have IPv6 connectivity to the

Internet)

You can add a contact from a peer that you have discovered, by calling the PeerNearMe

.AddToContactManager() method When you call this method you can choose to associate a display

name, nickname, and email address with the contact Typically, though, you will manage contacts by

using the ContactManager class

However you manipulate contacts, you will be dealing with PeerContact objects PeerContact , like

PeerNearMe , inherits from the abstract Peer base class PeerContact has more properties and methods

than PeerNearMe PeerContact includes DisplayName and EmailAddress properties that further

describe a PNM peer, for example Another difference between these two types is that PeerContact has

a more explicit relationship with the System.Net.PeerToPeer.PeerName class You can get a

PeerName from a PeerContact through the PeerContact.PeerName property Once you have done

this you can proceed to use techniques you looked at earlier to communicate with any endpoints the

PeerName exposes

Information about the local peer is also accessible through the ContactManager class, through the static

ContactManager.LocalContact property This gets you a PeerContact property with details of the

local peer

You can add PeerNearMe objects to the local list of contacts by using either the ContactManager

.CreateContact() or CreateContactAsync() method, or PeerName objects by using the

GetContact() method You can remove contacts represented by a PeerNearMe or PeerName object

with the DeleteContact() method

Finally, there are events that you can handle to respond to changes to contacts For example, you can use

the PresenceChanged event to respond to changes of presence for any of the contacts known by the

ContactManager

Sample Application

There is a second sample application in the downloadable code for this chapter that illustrates the use of

classes in the System.Net.PeerToPeer.Collaboration namespace This application is similar to the

other sample, but much simpler You will need two computers that can both sign in to the PNM server in

order to see this application in action, because it enumerates and displays PNM peers from the local subnet

When you run the application with at least one peer available for discovery the display will be similar to

Figure 47 - 8

Figure 47-8

The code is structured in the same way as the previous example, so if you ’ ve read through that code you

should be familiar with this code This time there is not much work to do in the Window_Loaded()

event handler except sign in, because there is no WCF service to initialize or peer name registration to

achieve:

Trang 37

Chapter 47: Peer-to-Peer Networking

private void Window_Loaded(object sender, RoutedEventArgs e){

// Sign in to PNM PeerCollaboration.SignIn(PeerScope.NearMe);

To make things look a little nicer, though, ContactManager.LocalContact.Nickname is used to format the window title:

// Get local peer name to display this.Title = string.Format(“PNMSample - {0}”, ContactManager.LocalContact.Nickname);

}

In Window_Closing() the local peer is automatically signed out of PNM:

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e){

// Sign out of PNM PeerCollaboration.SignOut(PeerScope.NearMe);

}

Most of the work is done in the RefreshButton_Click() event handler This uses the

PeerCollaboration.GetPeersNearMe() method to obtain a list of peers, and add those peers to the display using the PeerEntry class defined in the project, or a failure message if none are found

private void RefreshButton_Click(object sender, RoutedEventArgs e){

// Get local peers PeerNearMeCollection peersNearMe = PeerCollaboration.GetPeersNearMe();

// Prepare for new peers PeerList.Items.Clear();

// Examine peers foreach (PeerNearMe peerNearMe in peersNearMe) {

PeerList.Items.Add(

new PeerEntry {

PeerNearMe = peerNearMe, PresenceStatus = peerNearMe.GetPresenceInfo(

peerNearMe.PeerEndPoints[0]).PresenceStatus, DisplayString = peerNearMe.Nickname

});

} // Add failure message if necessary

if (PeerList.Items.Count == 0) {

PeerList.Items.Add(

new PeerEntry {

DisplayString = “No peers found.”

});

}}

Trang 38

Part VI: Communication

1642

As you can see from this example, interacting with the PNM service is made very simple by the classes

you have learned about

Summar y

This chapter demonstrated how to implement peer - to - peer (P2P) functionality in your applications by

using the new P2P classes in NET 3.5

You have looked at the types of solutions that P2P makes possible and how these solutions are

structured, how to use PNRP and PNM, and how to use the types in the System.Net.PeerToPeer and

System.Net.PeerToPeer.Collaboration namespace You also saw the extremely useful technique of

exposing WCF services as P2P endpoints

If you are interested in developing P2P applications it is well worth investigating PNM further It is also

worth looking at the peer channel, by which WCF services can broadcast communications among

multiple clients simultaneously

In the next chapter you look at syndication, and you see how you can expose data in RSS and Atom feeds

Trang 39

Syndication

Do you have some structured data to offer, data that changes from time to time? With many web sites, RSS or Atom symbols allow you to subscribe with feed readers Really Simple Syndication (RSS) is an XML format that allows syndicate information RSS became very popular with blogs

This XML information makes it easy to subscribe to using RSS readers

Nowadays, RSS is not only used with blogs but with many different data sources, such as online news magazines Any data that changes from time to time is offered by RSS or by its successor protocol Atom Internet Explorer 7 and Outlook 2007 offer RSS and Atom readers that are integrated into the product

NET 3.5 extends Windows Communication Foundation (WCF) with syndication features

Syndication classes are defined within the namespace System.ServiceModel.Syndication This namespace provides classes that can be used to both read and write RSS and Atom feeds

This chapter shows you how to create syndication readers, as well as how data can be offered This chapter offers the following:

An overview of System.ServiceModel.Syndication Information about Syndication Reader

Information about Syndication Feeds

Over view of System.Ser vicemodel.

is the abbreviation for Resource Description Framework The first version was created by Netscape

to describe content of its portal site It became successful when the New York Times began offering

its readers subscriptions to RSS news feeds in 2002 Figure 48 - 1 shows the RSS logo If a site shows this logo, then an RSS feed is offered

Trang 40

Part VI: Communication

Atom was designed to be the successor for RSS and is a proposed standard with RFC 4287:

www.ietf.org/rfc/rfc4287.txt The major difference between RSS and Atom is in the content that can

be defined with an item With RSS, the description element can contain simple text or HTML content in

which the reading application does not care about this content Atom requires that you define a specific type

for the content with a type attribute, and it also allows you to have XML content with defined namespaces

The following table lists classes from NET 3.5 that allow you to create a syndication feed These classes

are independent of the syndication type, RSS or Atom

Figure 48 - 1

SyndicationFeed SyndicationFeed represents the top-level element of a feed With Atom,

the top-level element is <feed>; RSS defines <rss> as the top-level element

With the static method Load(), a feed can be read using an XmlReader.Properties of this class such as Authors, Categories, Contributors,

Copyright, Description, ImageUrl, Links, Title, and Items allow you

to define child elements

SyndicationPerson SyndicationPerson represents a person with Name, Email, and Uri that

can be assigned to the Authors and Contributors collection

SyndicationItem A feed consists of multiple items Some of the properties of an item are

Authors, Contributors, Copyright, and Content

SyndicationLink SyndicationLink represents a link within a feed or an item This class

defines the properties Title and Uri

SyndicationCategory A feed can group items into categories The keyword of a category can be

set to the Name and Label properties of SyndicationCategory

SyndicationContent SyndicationContent is an abstract base class that describes the content

of an item Content can be of type HTML, plain text, XHTML, XML, or a URL, described with the concrete classes TextSyndicationContent,

UrlSyndicationContent, and XmlSyndicationContent

SyndicationElement-Extension

With an extension element, you can add additional content

The SyndicationElementExtension can be used to add information to a feed, a category, a person, a link, and an item

Ngày đăng: 12/08/2014, 23:23

TỪ KHÓA LIÊN QUAN