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 1Chapter 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 2Part 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 3Chapter 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 4Part 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 5Chapter 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 6Part 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 7Chapter 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 8Part 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 9Chapter 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 10Part 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 11Chapter 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 12Part 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 13Chapter 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 14Part 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 15Chapter 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 16Part 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 17Chapter 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 18Part 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 19Chapter 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 21Peer - 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 22Part 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 23Chapter 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 24Part 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 25Chapter 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 26Part 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 27Chapter 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 28Part 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 29Chapter 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 30Part 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 31Chapter 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 32Part 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 33Chapter 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 34Part 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 35Chapter 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 36Part 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 37Chapter 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 38Part 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 39Syndication
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 40Part 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