달력

32024  이전 다음

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

 

원본 : http://www.codeproject.com/Articles/17973/How-To-Get-Hardware-Information-CPU-ID-MainBoard-I

 

Screenshot - Article.jpg

Introduction

For numerous reasons, you may need to access system hardware information. One example is when you have created a software and you want to put a lock on that software.

One of the most efficient ways to do so is to get one of the IDs of a piece of hardware in the target machine and then plan your software so that it can just work with the computer that has the same ID. Since some hardware IDs (and not all of them) are unique, you have very simply created a lock!

Notice

In the first versions of this article, I thought that the Processor ID was unique, but some other users told me that they had tested the Processor ID in different computers and sometimes they give the same result. So you should consider this and not try to just rely on Processor ID. I suggest that you combine it with some other IDs such as Motherboard, USB Controller, Bios, CD-ROM Drive and so on.

Another example is the need to get hard disk information, its partitions, and the free space on each partition. You may also require the hardware information while working on a multimedia application when you need to have complete information about the sound device and the VGA device of the target machine. An excellent use of this application is getting information about the memory of the system and valuable information such as the device memory address, memory device and so on.

Using the Code

First of all, you have to know that the application function is using some System.Management classes. So you need to add this line of code:

using System.Management;

To get hardware information, you need to create an object of ManagementObjectSearcher class.

ManagementObjectSearcher searcher = new ManagementObjectSearcher(
    "select * from " + Key);

The Key on the code above, is a variable that is replaced with appropriate data. For example, to get the information of the CPU, you have to replace the Key with Win32_Processor. A complete list of these keys is given below:

Win32_1394Controller
Win32_1394ControllerDevice
Win32_Account
Win32_AccountSID
Win32_ACE
Win32_ActionCheck
Win32_AllocatedResource
Win32_ApplicationCommandLine
Win32_ApplicationService
Win32_AssociatedBattery
Win32_AssociatedProcessorMemory
Win32_BaseBoard
Win32_BaseService
Win32_Battery
Win32_Binary
Win32_BindImageAction
Win32_BIOS
Win32_BootConfiguration
Win32_Bus
Win32_CacheMemory
Win32_CDROMDrive
Win32_CheckCheck
Win32_CIMLogicalDeviceCIMDataFile
Win32_ClassicCOMApplicationClasses
Win32_ClassicCOMClass
Win32_ClassicCOMClassSetting
Win32_ClassicCOMClassSettings
Win32_ClassInfoAction
Win32_ClientApplicationSetting
Win32_CodecFile
Win32_COMApplication
Win32_COMApplicationClasses
Win32_COMApplicationSettings
Win32_COMClass
Win32_ComClassAutoEmulator
Win32_ComClassEmulator
Win32_CommandLineAccess
Win32_ComponentCategory
Win32_ComputerSystem
Win32_ComputerSystemProcessor
Win32_ComputerSystemProduct
Win32_COMSetting
Win32_Condition
Win32_CreateFolderAction
Win32_CurrentProbe
Win32_DCOMApplication
Win32_DCOMApplicationAccessAllowedSetting
Win32_DCOMApplicationLaunchAllowedSetting
Win32_DCOMApplicationSetting
Win32_DependentService
Win32_Desktop
Win32_DesktopMonitor
Win32_DeviceBus
Win32_DeviceMemoryAddress
Win32_DeviceSettings
Win32_Directory
Win32_DirectorySpecification
Win32_DiskDrive
Win32_DiskDriveToDiskPartition
Win32_DiskPartition
Win32_DisplayConfiguration
Win32_DisplayControllerConfiguration
Win32_DMAChannel
Win32_DriverVXD
Win32_DuplicateFileAction
Win32_Environment
Win32_EnvironmentSpecification
Win32_ExtensionInfoAction
Win32_Fan
Win32_FileSpecification
Win32_FloppyController
Win32_FloppyDrive
Win32_FontInfoAction
Win32_Group
Win32_GroupUser
Win32_HeatPipe
Win32_IDEController
Win32_IDEControllerDevice
Win32_ImplementedCategory
Win32_InfraredDevice
Win32_IniFileSpecification
Win32_InstalledSoftwareElement
Win32_IRQResource
Win32_Keyboard
Win32_LaunchCondition
Win32_LoadOrderGroup
Win32_LoadOrderGroupServiceDependencies
Win32_LoadOrderGroupServiceMembers
Win32_LogicalDisk
Win32_LogicalDiskRootDirectory
Win32_LogicalDiskToPartition
Win32_LogicalFileAccess
Win32_LogicalFileAuditing
Win32_LogicalFileGroup
Win32_LogicalFileOwner
Win32_LogicalFileSecuritySetting
Win32_LogicalMemoryConfiguration
Win32_LogicalProgramGroup
Win32_LogicalProgramGroupDirectory
Win32_LogicalProgramGroupItem
Win32_LogicalProgramGroupItemDataFile
Win32_LogicalShareAccess
Win32_LogicalShareAuditing
Win32_LogicalShareSecuritySetting
Win32_ManagedSystemElementResource
Win32_MemoryArray
Win32_MemoryArrayLocation
Win32_MemoryDevice
Win32_MemoryDeviceArray
Win32_MemoryDeviceLocation
Win32_MethodParameterClass
Win32_MIMEInfoAction
Win32_MotherboardDevice
Win32_MoveFileAction
Win32_MSIResource
Win32_NetworkAdapter
Win32_NetworkAdapterConfiguration
Win32_NetworkAdapterSetting
Win32_NetworkClient
Win32_NetworkConnection
Win32_NetworkLoginProfile
Win32_NetworkProtocol
Win32_NTEventlogFile
Win32_NTLogEvent
Win32_NTLogEventComputer
Win32_NTLogEventLog
Win32_NTLogEventUser
Win32_ODBCAttribute
Win32_ODBCDataSourceAttribute
Win32_ODBCDataSourceSpecification
Win32_ODBCDriverAttribute
Win32_ODBCDriverSoftwareElement
Win32_ODBCDriverSpecification
Win32_ODBCSourceAttribute
Win32_ODBCTranslatorSpecification
Win32_OnBoardDevice
Win32_OperatingSystem
Win32_OperatingSystemQFE
Win32_OSRecoveryConfiguration
Win32_PageFile
Win32_PageFileElementSetting
Win32_PageFileSetting
Win32_PageFileUsage
Win32_ParallelPort
Win32_Patch
Win32_PatchFile
Win32_PatchPackage
Win32_PCMCIAController
Win32_Perf
Win32_PerfRawData
Win32_PerfRawData_ASP_ActiveServerPages
Win32_PerfRawData_ASPNET_114322_ASPNETAppsv114322
Win32_PerfRawData_ASPNET_114322_ASPNETv114322
Win32_PerfRawData_ASPNET_ASPNET
Win32_PerfRawData_ASPNET_ASPNETApplications
Win32_PerfRawData_IAS_IASAccountingClients
Win32_PerfRawData_IAS_IASAccountingServer
Win32_PerfRawData_IAS_IASAuthenticationClients
Win32_PerfRawData_IAS_IASAuthenticationServer
Win32_PerfRawData_InetInfo_InternetInformationServicesGlobal
Win32_PerfRawData_MSDTC_DistributedTransactionCoordinator
Win32_PerfRawData_MSFTPSVC_FTPService
Win32_PerfRawData_MSSQLSERVER_SQLServerAccessMethods
Win32_PerfRawData_MSSQLSERVER_SQLServerBackupDevice
Win32_PerfRawData_MSSQLSERVER_SQLServerBufferManager
Win32_PerfRawData_MSSQLSERVER_SQLServerBufferPartition
Win32_PerfRawData_MSSQLSERVER_SQLServerCacheManager
Win32_PerfRawData_MSSQLSERVER_SQLServerDatabases
Win32_PerfRawData_MSSQLSERVER_SQLServerGeneralStatistics
Win32_PerfRawData_MSSQLSERVER_SQLServerLatches
Win32_PerfRawData_MSSQLSERVER_SQLServerLocks
Win32_PerfRawData_MSSQLSERVER_SQLServerMemoryManager
Win32_PerfRawData_MSSQLSERVER_SQLServerReplicationAgents
Win32_PerfRawData_MSSQLSERVER_SQLServerReplicationDist
Win32_PerfRawData_MSSQLSERVER_SQLServerReplicationLogreader
Win32_PerfRawData_MSSQLSERVER_SQLServerReplicationMerge
Win32_PerfRawData_MSSQLSERVER_SQLServerReplicationSnapshot
Win32_PerfRawData_MSSQLSERVER_SQLServerSQLStatistics
Win32_PerfRawData_MSSQLSERVER_SQLServerUserSettable
Win32_PerfRawData_NETFramework_NETCLRExceptions
Win32_PerfRawData_NETFramework_NETCLRInterop
Win32_PerfRawData_NETFramework_NETCLRJit
Win32_PerfRawData_NETFramework_NETCLRLoading
Win32_PerfRawData_NETFramework_NETCLRLocksAndThreads
Win32_PerfRawData_NETFramework_NETCLRMemory
Win32_PerfRawData_NETFramework_NETCLRRemoting
Win32_PerfRawData_NETFramework_NETCLRSecurity
Win32_PerfRawData_Outlook_Outlook
Win32_PerfRawData_PerfDisk_PhysicalDisk
Win32_PerfRawData_PerfNet_Browser
Win32_PerfRawData_PerfNet_Redirector
Win32_PerfRawData_PerfNet_Server
Win32_PerfRawData_PerfNet_ServerWorkQueues
Win32_PerfRawData_PerfOS_Cache
Win32_PerfRawData_PerfOS_Memory
Win32_PerfRawData_PerfOS_Objects
Win32_PerfRawData_PerfOS_PagingFile
Win32_PerfRawData_PerfOS_Processor
Win32_PerfRawData_PerfOS_System
Win32_PerfRawData_PerfProc_FullImage_Costly
Win32_PerfRawData_PerfProc_Image_Costly
Win32_PerfRawData_PerfProc_JobObject
Win32_PerfRawData_PerfProc_JobObjectDetails
Win32_PerfRawData_PerfProc_Process
Win32_PerfRawData_PerfProc_ProcessAddressSpace_Costly
Win32_PerfRawData_PerfProc_Thread
Win32_PerfRawData_PerfProc_ThreadDetails_Costly
Win32_PerfRawData_RemoteAccess_RASPort
Win32_PerfRawData_RemoteAccess_RASTotal
Win32_PerfRawData_RSVP_ACSPerRSVPService
Win32_PerfRawData_Spooler_PrintQueue
Win32_PerfRawData_TapiSrv_Telephony
Win32_PerfRawData_Tcpip_ICMP
Win32_PerfRawData_Tcpip_IP
Win32_PerfRawData_Tcpip_NBTConnection
Win32_PerfRawData_Tcpip_NetworkInterface
Win32_PerfRawData_Tcpip_TCP
Win32_PerfRawData_Tcpip_UDP
Win32_PerfRawData_W3SVC_WebService
Win32_PhysicalMemory
Win32_PhysicalMemoryArray
Win32_PhysicalMemoryLocation
Win32_PNPAllocatedResource
Win32_PnPDevice
Win32_PnPEntity
Win32_PointingDevice
Win32_PortableBattery
Win32_PortConnector
Win32_PortResource
Win32_POTSModem
Win32_POTSModemToSerialPort
Win32_PowerManagementEvent
Win32_Printer
Win32_PrinterConfiguration
Win32_PrinterController
Win32_PrinterDriverDll
Win32_PrinterSetting
Win32_PrinterShare
Win32_PrintJob
Win32_PrivilegesStatus
Win32_Process
Win32_Processor
Win32_ProcessStartup
Win32_Product
Win32_ProductCheck
Win32_ProductResource
Win32_ProductSoftwareFeatures
Win32_ProgIDSpecification
Win32_ProgramGroup
Win32_ProgramGroupContents
Win32_ProgramGroupOrItem
Win32_Property
Win32_ProtocolBinding
Win32_PublishComponentAction
Win32_QuickFixEngineering
Win32_Refrigeration
Win32_Registry
Win32_RegistryAction
Win32_RemoveFileAction
Win32_RemoveIniAction
Win32_ReserveCost
Win32_ScheduledJob
Win32_SCSIController
Win32_SCSIControllerDevice
Win32_SecurityDescriptor
Win32_SecuritySetting
Win32_SecuritySettingAccess
Win32_SecuritySettingAuditing
Win32_SecuritySettingGroup
Win32_SecuritySettingOfLogicalFile
Win32_SecuritySettingOfLogicalShare
Win32_SecuritySettingOfObject
Win32_SecuritySettingOwner
Win32_SelfRegModuleAction
Win32_SerialPort
Win32_SerialPortConfiguration
Win32_SerialPortSetting
Win32_Service
Win32_ServiceControl
Win32_ServiceSpecification
Win32_ServiceSpecificationService
Win32_SettingCheck
Win32_Share
Win32_ShareToDirectory
Win32_ShortcutAction
Win32_ShortcutFile
Win32_ShortcutSAP
Win32_SID
Win32_SMBIOSMemory
Win32_SoftwareElement
Win32_SoftwareElementAction
Win32_SoftwareElementCheck
Win32_SoftwareElementCondition
Win32_SoftwareElementResource
Win32_SoftwareFeature
Win32_SoftwareFeatureAction
Win32_SoftwareFeatureCheck
Win32_SoftwareFeatureParent
Win32_SoftwareFeatureSoftwareElements
Win32_SoundDevice
Win32_StartupCommand
Win32_SubDirectory
Win32_SystemAccount
Win32_SystemBIOS
Win32_SystemBootConfiguration
Win32_SystemDesktop
Win32_SystemDevices
Win32_SystemDriver
Win32_SystemDriverPNPEntity
Win32_SystemEnclosure
Win32_SystemLoadOrderGroups
Win32_SystemLogicalMemoryConfiguration
Win32_SystemMemoryResource
Win32_SystemNetworkConnections
Win32_SystemOperatingSystem
Win32_SystemPartitions
Win32_SystemProcesses
Win32_SystemProgramGroups
Win32_SystemResources
Win32_SystemServices
Win32_SystemSetting
Win32_SystemSlot
Win32_SystemSystemDriver
Win32_SystemTimeZone
Win32_SystemUsers
Win32_TapeDrive
Win32_TemperatureProbe
Win32_Thread
Win32_TimeZone
Win32_Trustee
Win32_TypeLibraryAction
Win32_UninterruptiblePowerSupply
Win32_USBController
Win32_USBControllerDevice
Win32_UserAccount
Win32_UserDesktop
Win32_VideoConfiguration
Win32_VideoController
Win32_VideoSettings
Win32_VoltageProbe
Win32_WMIElementSetting
Win32_WMISetting

First, call the Get() method of the ManagementObjectSearcher object that this application calls the searcher object. This will fill the object with the information you need. After that, you need to process the data that is in the searcher object.

foreach (ManagementObject share in searcher.Get())
{
// Some Codes ...

}

Each ManagementObject has some Properties that are useful for us. Of course the Data of those properties is needed and we can process them like this:

foreach (PropertyData PC in share.Properties)
{
 //some codes ...

}

The other parts of this application are nothing but working with the ListView control and that is really easy!

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

'.NET > C#' 카테고리의 다른 글

[C#] Network Driver Check  (0) 2011.08.11
[펌] Design Pattern --> C# @.@;  (0) 2009.12.24
[펌] System.Xml. XmlSerializer Examples  (0) 2009.08.31
ADHelper - An Active Directory Class  (0) 2009.08.17
C# - Dynamically Invoke Web Service At Runtime  (0) 2009.08.13
Posted by tornado
|

[C#] Network Driver Check

.NET/C# 2011. 8. 11. 10:49
[link] http://www.publicjoe.f9.co.uk/csharp/snip/snip010.html


C# Snippets


  1. How do I retreive the total space and free space for network drives?

  1. Here is how to get the free space of a networked drive using System.Management and WMI. It's important to note that the free space could be incorrect when user-quotas are applied.

using System;
using System.Management;

namespace snip010
{
  class NetworkSpace
  {
    static void Main(string[] args)
    {
      SelectQuery query = new SelectQuery(
          "select name, 
          FreeSpace from win32_logicaldisk where drivetype=4");

      ManagementObjectSearcher searcher = 
          new ManagementObjectSearcher(query);

      foreach (ManagementObject mo in searcher.Get())
      {
        Console.WriteLine("Drive letter is: {0}", mo["name"]);
        Console.WriteLine("Drive's free space is: {0}", 
                          mo["FreeSpace"]);
      }

      // Here to stop app from closing
      Console.WriteLine("\nPress Return to exit.");
      Console.Read();
    }
  }
}

I have created a little application to demo in C# Express.

A similar application to get the space on C: drive can be found here.

Note... In order to use the System.Management namespace in .NET 2, you will need to add a reference to the System.Management.dll. This can be done in C# Express by right-clicking on the project in the solution explorer and choosing Add Reference... from the list. The dll is on the first tab towards the end of the list of items.


References

For more information on the SelectQuery class visit MSDN at microsoft here.

For more information on the System.Management Namespace visit MSDN at microsoft here.

For more information on the ManagementObject class visit MSDN at microsoft here.

For more information on the ManagementObjectSearcher class visit MSDN at microsoft here.

For more information on the Win32_LogicalDisk class visit MSDN at microsoft here.

Back to Snippets.



Posted by tornado
|

[링크] http://www.dofactory.com/Patterns/Patterns.aspx

 

 

 

 

Design Patterns

Share/Save/Bookmark
Design patterns are recurring solutions to software design problems you find again and again in real-world application development. Patterns are about design and interaction of objects, as well as providing a communication platform concerning elegant, reusable solutions to commonly encountered programming challenges.

The Gang of Four (GoF) patterns are generally considered the foundation for all other patterns. They are categorized in three groups: Creational, Structural, and Behavioral. Here you will find information on these important patterns.

To give you a head start, the C# source code is provided in 2 forms: 'structural' and 'real-world'. Structural code uses type names as defined in the pattern definition and UML diagrams. Real-world code provides real-world programming situations where you may use these patterns.

A third form, '.NET optimized' demonstrates design patterns that exploit built-in .NET 2.0, 3.0, and 3.5 features, such as, generics, attributes, delegates, object and collection initializers, automatic properties, and reflection. These and much more are available in our Design Pattern Framework 3.5TM. See our Singleton page for a .NET 3.5 Optimized code sample.

Posted by tornado
|
출처 : http://www.eggheadcafe.com/articles/system.xml.xmlserialization.asp



Related Articles: XmlSerializer to Serialize Class to Xml and Bulk Load Data
 
Object Serialization is a process through which an object's state is transformed into some serial data format, such as XML or a binary format.  For example, you can serialize an object and transport it over the Internet using HTTP between a client and a server.  On the other end, deserialization reconstructs the object from the stream.  XML was designed to be a technology for data exchange across heterogeneous systems.  It can be easily transmitted between distributed components because of its platform independence and its simple, text-based, self-describing format.  In this article I will discuss the serialization of custom business objects to XML and reconstruction the object back from the XML string, using C#.  This process is called XML serialization and is very well supported by .Net.
 
Some good uses for serialization/deserialization include:
  • Storing user preferences in an object.
  • Maintaining security information across pages and applications.
  • Modification of XML documents without using the DOM.
  • Passing an object from one application to another.
  • Passing an object from one domain to another.
  • Passing an object through a firewall as an XML string.
  • These are only a few of the many possibilities that serialization opens up for us.
     
    Suppose we have an object defined and instantiated as shown below:
     class Person
      {
         private String personName;
         private Int32 personAge;
         public String Name
         { 
           get { return personName;  } 
           set { personName = value; }
         } 
         public Int32 Age 
         {
           get { return personAge; }
           set { personAge = value;}
         }
       }
    
    
    Instantiation of the class is shown below:
       Person oPerson = new Person(); 
       oPerson.Name = "Anthony";
       oPerson.Age = 38;  
    
    
     
    Let's say that for some reason, we wanted to save a copy of this object just as it is at this very moment.  We could serialize it as an XML document that would look something like this:
    Anthony 38
    Then, at some later time when we needed to use the object again, we could just deserialize it and have our object restored to us just as it was at the moment we serialized it.  However XML serialization follows certain rules and the classes defined by us have to conform to these rules.
    1. XML serialization serializes only the public fields and property values of an object into an XML stream
    2. XML serialization does not include type information
    3. XML serialization requires a default constructor to be declared in the class that is to be serialized
    4. XML serialization requires all properties that are to be serialized as read write properties.  Read only properties are not serialized.
     
    Serialize class
    The .Net framework provides a rich collection of classes for serialization.  As we are concerned with XML serialization, we will examine the use of the XmlSerializer class.  This is the central class for XML serialization and its most important methods are the Serialize and Deserialize methods.  Most of the articles discussing XML serialization use IO streams for saving serialized data into XML files, however in my example I will demonstrate the use of XML serialization to convert an object into XML string and store it in a memory stream instead of an XML file.  This technique is extremely useful in cases where a complex object is to be converted to XML string and its properties have to be manipulated and then the object recreated from the XML string on the fly!!
     
    Attributes
    Before we start with the process of demonstrating the technique of XML serialization let us have a small discussion on Metadata Attributes.  A complete discussion of Metadata Attributes is beyond the scope of this article.  Nevertheless we need to understand the concepts behind metadata attributes to understand how XML serialization works.  Attributes are annotations to an interface or a class definition to specify certain behavior.  Assemblies, classes, fields and methods, each can have attributes.  Some are used by the compiler, some are used by the runtime, e.g. to identify a method requires a call to a web service, or how to serialize a class to XML.  There is very little overhead associated when using attributes.  Attaching attributes to a class is done directly in the source code.
     
    The syntax to initialize a metadata attribute and attach
    it to a class or a method in C# is either:
     [Attribute(constructor-parameters-list )] 
    or: 
    [Attribute(constructor-parameters-list, property=value, ... )] 
    
    // The System.Xml.Serialization namespace introduces a set of attributes
    // to control how classes are mapped to XML.  Let's look at a quick example:
    // One of the attributes used with XML serialization is the XmlRootAttribute
    // to change the name of the root element of a serialization hierarchy."
    
    You would add the XmlRootAttribute to a class like this: 
    
    using System.Xml.Serialization; 
    [XmlRootAttribute(Name="Human", IsNullable=false)] 
    public class Person 
    { 
    // class implementation goes here 
    } 
    
    
     
    If we make this change in the class declaration of listing 1, and serialize the instance of this class we will get the XML generated resembling the one shown below:
    <Human> 
    <Name>Anthony</Name> 
    <Age>38</Age> 
    </Human> 
    
    
     
    We would be using UTF-8 encoding for the creating the XML stream for the custom object as it supports a wide range of Unicode character values and surrogates.  For this purpose, we will make use of the UTF8Encoding class provided by the .Net framework.  For more information on UTF8Encoding you may want to refer to MSDN at:
    UTF8Encoding
    For explaining the concept of XML serialization I will create and use a demo class Animal with a number of properties that describe an animal such as name, length, weight, place of existence etc.  First create a C# console application using the Visual Studio.Net and name the project as MySerializer.  This will automatically create a namespace MySerializer.  Import the following namespaces into the project:
    1. System.Xml.Serialization
    2. System.IO
    3. System.Text
     
    Now add a new class and name it Animal.  The code for this class is given below:
     using System;
     
     namespace MySerializer
     {
         [XmlRootAttribute(ElementName="WildAnimal", IsNullable=false)]
         public class Animal
         {
           public Animal() {   }
    
           private String animalName;
           private String foodTypeCategory;
           private Boolean isDomesticed;
           private String placeOfExistence;
           private Int32 length;
           private Int32 height;
           private Int32 weight;
           public String AnimalName
                 { get { return animalName; } set { animalName = value; } }
           public String FoodTypeCategory 
                  { get { return foodTypeCategory; }
                    set { foodTypeCategory = value; } }
           public Boolean IsDomesticed
             { get { return isDomesticed; } 
               set { isDomesticed = value; } } 
           public String PlaceOfExistence 
               { get { return placeOfExistence; } set { placeOfExistence = value; } } 
           public Int32 Length { get { return length; } set { length = value; } }
           public Int32 Height { get { return height; } set { height = value; } } 
           public Int32 Weight { get { return weight; } set { weight = value; } } 
         }
     } 
    
    
     
    Listing 2: Custom class whose objects are to be serialized to XML
    When you create the C# console application a default class by the name of Class1 gets added to the project.  This class contains the definition of the Main function that is the entry point of the assembly.  Rename this class from "Class1" to clsMain.  In this class clsMain, we will define the custom methods to serialize and deserialize the object of the class Animal. Apart from these there are helper methods to convert a UTF-8 byte array to string and vice - versa.
    The definition of these methods is given below:
    private String UTF8ByteArrayToString(Byte[] characters) 
    { 
    
      UTF8Encoding encoding = new UTF8Encoding();
      String constructedString = encoding.GetString(characters);
      return (constructedString);
     }
     
     private Byte[] StringToUTF8ByteArray(String pXmlString)
     {
         UTF8Encoding encoding = new UTF8Encoding();
         Byte[] byteArray = encoding.GetBytes(pXmlString);
         return byteArray;
     } 
    
    
     
    Listing 3: Helper Methods
    Serialization
    Now we will add a custom method SerializeObject to the class clsMain which will use the XmlSerializer to serialize the object to XML string.
    The code for this method is given below:
     
    public String SerializeObject(Object pObject) 
    {
        try 
        {
          String XmlizedString = null;
          MemoryStream memoryStream = new MemoryStream();
          XmlSerializer xs = new XmlSerializer(typeof(Animal));
          XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
          xs.Serialize(xmlTextWriter, pObject);
          memoryStream = (MemoryStream)xmlTextWriter.BaseStream;
          XmlizedString = UTF8ByteArrayToString(memoryStream.ToArray());
          return XmlizedString;
         }
         catch (Exception e) { System.Console.WriteLine(e); return null; }
     } 
    
    
     
    Listing 4: Method to serialize the custom business object
    Let us look through the code carefully.  First we create an instance of the MemoryStream class since we intend to keep the XML string in memory instead of saving it to a file.  Instantiate the XmlSerializer class with the type of the class that is to be serialized.  In our case this is the class Animal.  Now we use the XmlTextWriter Class that provides a fast, non-cached, forward-only way of generating streams or files containing XML data.  Notice that we use an overloaded constructor of the XmlTextWriter class and set the encoding to UTF-8.  This means that the XML document will be created with UTF-8 encoding.
    Now, we go to the most important step and that is calling one of the overloaded implementations of the Serialize method of the XmlSerializer object.  We now read the XML stream generated by the serializer from the BaseStream of the XmlTextWriter.  Since the BaseStream property returned by the XmlTextWriter is of the type System.IO.Stream, we can easily type cast it into MemoryStream (which is derived from System.IO.Stream) for our purpose.
    We now pass the Byte[] array returned by the ToArray() method of the MemoryStream object to the helper function UTF8ByteArrayToString.  This method constructs the XML string from the binary array and returns the same.  This small piece of trick is performed by the overloaded implementation of the GetString() method of the UTF8Encoding class.
     
    The output that is generated is given below:
    <?xml version="1.0" encoding="utf-8"?> 
    <WildAnimal xmlns:xsd="http://www.w3.org/2001/XMLSchema"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
    <AnimalName>Lion</AnimalName> 
    <FoodTypeCategory>Carnivore</FoodTypeCategory> 
    <IsDomesticed>false</IsDomesticed> 
    <PlaceOfExistence>Africa</PlaceOfExistence> 
    <Length>15</Length> 
    <Height>4</Height> 
    <Weight>900</Weight> 
    </WildAnimal> 
    
    
     
    Notice that all the properties defined in the class Animal are serialized into nodes and the name of the root also gets changed from Animal to WildAnimal, this is because we had included the attribute XmlRootAttribute in the declaration of the class Animal.
    Deserialization
    Just as we had created a custom method to serialize the object, we will create another method in the class clsMain which will use the Deserialize() method of the XmlSerializer class to recreate the object from the XML string.
    The code for this method is given below:
     public Object DeserializeObject(String pXmlizedString)
     {
         XmlSerializer xs = new XmlSerializer(typeof(Automobile));
         MemoryStream memoryStream = new MemoryStream(StringToUTF8ByteArray(pXmlizedString));
         XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
         return xs.Deserialize(memoryStream);
      } 
    
    
    To get the Animal object back from the XML string you will simply call this method and pass the XML string.  It will first convert the XML string to a byte array (exactly the reverse of what we did in serialization) and then recreate the object from the XmlSerializer.
    There you are!!  You have now learnt the entire process of serializing a custom object into an XML string and keeping it in an in memory variable and then reconstructing it back using deserialization.  With the help of powerful XMLDocument class and other rich XML parsing classes and readers (such as XmlReader) provided by .Net you can change values or nodes , change attributes and apply advanced XML processing functions to XML document.

    '.NET > C#' 카테고리의 다른 글

    [C#] Network Driver Check  (0) 2011.08.11
    [펌] Design Pattern --> C# @.@;  (0) 2009.12.24
    ADHelper - An Active Directory Class  (0) 2009.08.17
    C# - Dynamically Invoke Web Service At Runtime  (0) 2009.08.13
    Change the group of a user in AD through C#  (0) 2009.06.16
    Posted by tornado
    |
    출처 : http://www.c-sharpcorner.com/uploadfile/craig_aroa/adhelper08232005064459am/adhelper.aspx?login=true&user=yheesung


    흠..  예전거 보다 더 좋아진것 같습니다.
    앞으로는 누가 더 검색 잘하고, 누가 더 짜집기 잘 하느냐에 따라 프로그래머의 레벨이 정해지지 않을까...


    Working on my latest project required quite a degree of development against the Active Directory. Not having had any AD experience before slowed me down for a while, but after trawling through lost of news groups etc, I gathered enough information to do what I needed to do.

    I have developed an ADHelper class that will should take care of most AD requirements, from :-

    • creating/updating a user
    • assigning a user to a group
    • enabling/disabling their account
    • setting password
    • checking if a user is valid
    • loggin in
    • checking if a user exists
    • performing queries

    All while returning DataSets to allow easy binding to datagrids etc.

    Im by no means an expert, but hopefully this helps some of you facing the same problems that I had.

    NOTES
    =====

    This class will return a mix of ActiveDirectory 'DirectoryEntry' objects and DataSets that u can plug into your business layer

    The first couple of lines that set the following private varables, need to be changed so that the 'Configuration' is the .Net ConfigurationSettings.AppSettings["xxx"].ToString, as the configuration object is a custom one that has not been included.

    private static string ADPath=Configuration.ADFullPath ;
    private static string
    ADUser = Configuration.ADAdminUser ;
    private static string ADPassword = Configuration.ADAdminPassword ;

    Change to :-

    private static string ADPath=ConfigurationSettings.AppSettings["ADFullPath"].ToString() ;
    private static string
    ADUser = ConfigurationSettings.AppSettings["ADAdminUser"].ToString() ;
    private static string ADPassword = ConfigurationSettings.AppSettings["ADAdminPassword"].ToString() ;



    Posted by tornado
    |

    출처 : http://www.crowsprogramming.com/archives/66


    C# - Dynamically Invoke Web Service At Runtime

    Web services have become an integral part of the communications between software over a network. There are a lot of ugly details behind how they work but thankfully C# hide a lot of those gory details. What you need to know is that there is a web service description (WSDL) that describes the available services and the data types they use (via XML).

    You can use the WSDL to create types in C# that can communicate with the web service. There are a couple of ways that you can do this. You can generate a proxy class using the WSDL utility in the .NET SDK and add it to your project. You can also download the WSDL file at runtime, create the proxy class, compile it into an assembly, and execute the methods on it. This allows you to dynamically invoke methods from a web service that aren’t known at build time and is the approach I will explore today.

    Step 1 – Obtain a ServiceDescriptionImporter

    We want to create a ServiceDescriptionImporter object that we can use to generate the web service proxy class. We have to pass to it the WSDL location which is an XML document.

    XmlTextReader xmlreader = new XmlTextReader(“MyWebService” + "?wsdl");
     
    ServiceDescription serviceDescription = ServiceDescription.Read(xmlreader);
     
    // build an importer, that assumes the SOAP protocol, client binding, and generates properties
    ServiceDescriptionImporter descriptionImporter = new ServiceDescriptionImporter();
    descriptionImporter.ProtocolName = "Soap";
    descriptionImporter.AddServiceDescription(serviceDescription, null, null);
    descriptionImporter.Style = ServiceDescriptionImportStyle.Client;
    descriptionImporter.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateProperties;

    Step 2 – Compile an Assembly from the importer

    Now that we have the ServiceDescriptionImporter we need to use it to compile a proxy assembly that we can use to communicate with the web service. The assembly will exist in the users’ temporary folder.

    // a namespace and compile unit are needed by importer
    CodeNamespace codeNamespace = new CodeNamespace();
    CodeCompileUnit codeUnit = new CodeCompileUnit();
     
    codeUnit.Namespaces.Add(codeNamespace);
     
    ServiceDescriptionImportWarnings importWarnings = descriptionImporter.Import(codeNamespace, codeUnit);
     
    if (importWarnings == 0) // no warnings
    {
         // create a c# compiler
         CodeDomProvider compiler = CodeDomProvider.CreateProvider("CSharp");
     
         // include the assembly references needed to compile
         string[] references = new string[2] { "System.Web.Services.dll", "System.Xml.dll" };
     
         CompilerParameters parameters = new CompilerParameters(references);
     
         // compile into assembly
         CompilerResults results = compiler.CompileAssemblyFromDom(parameters, codeUnit);
     
         foreach (CompilerError oops in results.Errors)
         {
              // trap these errors and make them available to exception object
              throw new Exception("Compilation Error Creating Assembly");
         }
     
         // all done....
         return results.CompiledAssembly;
    }
    else
    {
         // warnings issued from importers, something wrong with WSDL
         throw new Exception("Invalid WSDL");
    }

    Step 3 – Execute Methods from the Web Service

    Finally we have an assembly built from the web service description and now we can invoke methods from it just like any other ordinary assembly we might use. To accomplish this we must use reflection to discover the correct method.

    object obj = this.webServiceAssembly.CreateInstance(serviceName);
     
    Type type = obj.GetType();
     
    return (T)type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, obj, args);

    And there you have it, dynamic invocation of web service methods at runtime. Using this technique you can invoke methods from any arbitrary web service at runtime.

    Here is a full example class for dynamically invoking a web service method in C#.

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Reflection;
    using System.CodeDom;
    using System.CodeDom.Compiler;
    using System.Web.Services;
    using System.Web.Services.Description;
    using System.Web.Services.Discovery;
    using System.Xml;
     
    namespace DynamicWebExample
    {
        class WebServiceInvoker
        {
            Dictionary<string, Type> availableTypes;
     
            /// <summary>
            /// Text description of the available services within this web service.
            /// </summary>
            public List<string> AvailableServices
            {
                get{ return this.services; }
            }
     
            /// <summary>
            /// Creates the service invoker using the specified web service.
            /// </summary>
            /// <param name="webServiceUri"></param>
            public WebServiceInvoker(Uri webServiceUri)
            {
                this.services = new List<string>(); // available services
                this.availableTypes = new Dictionary<string, Type>(); // available types
     
                // create an assembly from the web service description
                this.webServiceAssembly = BuildAssemblyFromWSDL(webServiceUri);
     
                // see what service types are available
                Type[] types = this.webServiceAssembly.GetExportedTypes();
     
                // and save them
                foreach (Type type in types)
                {
                    services.Add(type.FullName);
                    availableTypes.Add(type.FullName, type);
                }
            }
     
            /// <summary>
            /// Gets a list of all methods available for the specified service.
            /// </summary>
            /// <param name="serviceName"></param>
            /// <returns></returns>
            public List<string> EnumerateServiceMethods(string serviceName)
            {
                List<string> methods = new List<string>();
     
                if (!this.availableTypes.ContainsKey(serviceName))
                    throw new Exception("Service Not Available");
                else
                {
                    Type type = this.availableTypes[serviceName];
     
                    // only find methods of this object type (the one we generated)
                    // we don't want inherited members (this type inherited from SoapHttpClientProtocol)
                    foreach (MethodInfo minfo in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
                        methods.Add(minfo.Name);
     
                    return methods;
                }
            }
     
            /// <summary>
            /// Invokes the specified method of the named service.
            /// </summary>
            /// <typeparam name="T">The expected return type.</typeparam>
            /// <param name="serviceName">The name of the service to use.</param>
            /// <param name="methodName">The name of the method to call.</param>
            /// <param name="args">The arguments to the method.</param>
            /// <returns>The return value from the web service method.</returns>
            public T InvokeMethod<T>( string serviceName, string methodName, params object[] args )
            {
                // create an instance of the specified service
                // and invoke the method
                object obj = this.webServiceAssembly.CreateInstance(serviceName);
     
                Type type = obj.GetType();
     
                return (T)type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, obj, args);
            }
     
            /// <summary>
            /// Builds the web service description importer, which allows us to generate a proxy class based on the 
            /// content of the WSDL described by the XmlTextReader.
            /// </summary>
            /// <param name="xmlreader">The WSDL content, described by XML.</param>
            /// <returns>A ServiceDescriptionImporter that can be used to create a proxy class.</returns>
            private ServiceDescriptionImporter BuildServiceDescriptionImporter( XmlTextReader xmlreader )
            {
                // make sure xml describes a valid wsdl
                if (!ServiceDescription.CanRead(xmlreader))
                    throw new Exception("Invalid Web Service Description");
     
                // parse wsdl
                ServiceDescription serviceDescription = ServiceDescription.Read(xmlreader);
     
                // build an importer, that assumes the SOAP protocol, client binding, and generates properties
                ServiceDescriptionImporter descriptionImporter = new ServiceDescriptionImporter();
                descriptionImporter.ProtocolName = "Soap";
                descriptionImporter.AddServiceDescription(serviceDescription, null, null);
                descriptionImporter.Style = ServiceDescriptionImportStyle.Client;
                descriptionImporter.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateProperties;
     
                return descriptionImporter;
            }
     
            /// <summary>
            /// Compiles an assembly from the proxy class provided by the ServiceDescriptionImporter.
            /// </summary>
            /// <param name="descriptionImporter"></param>
            /// <returns>An assembly that can be used to execute the web service methods.</returns>
            private Assembly CompileAssembly(ServiceDescriptionImporter descriptionImporter)
            {
                // a namespace and compile unit are needed by importer
                CodeNamespace codeNamespace = new CodeNamespace();
                CodeCompileUnit codeUnit = new CodeCompileUnit();
     
                codeUnit.Namespaces.Add(codeNamespace);
     
                ServiceDescriptionImportWarnings importWarnings = descriptionImporter.Import(codeNamespace, codeUnit);
     
                if (importWarnings == 0) // no warnings
                {
                    // create a c# compiler
                    CodeDomProvider compiler = CodeDomProvider.CreateProvider("CSharp");
     
                    // include the assembly references needed to compile
                    string[] references = new string[2] { "System.Web.Services.dll", "System.Xml.dll" };
     
                    CompilerParameters parameters = new CompilerParameters(references);
     
                    // compile into assembly
                    CompilerResults results = compiler.CompileAssemblyFromDom(parameters, codeUnit);
     
                    foreach (CompilerError oops in results.Errors)
                    {
                        // trap these errors and make them available to exception object
                        throw new Exception("Compilation Error Creating Assembly");
                    }
     
                    // all done....
                    return results.CompiledAssembly;
                }
                else
                {
                    // warnings issued from importers, something wrong with WSDL
                    throw new Exception("Invalid WSDL");
                }
            }
     
            /// <summary>
            /// Builds an assembly from a web service description.
            /// The assembly can be used to execute the web service methods.
            /// </summary>
            /// <param name="webServiceUri">Location of WSDL.</param>
            /// <returns>A web service assembly.</returns>
            private Assembly BuildAssemblyFromWSDL(Uri webServiceUri)
            {
                if (String.IsNullOrEmpty(webServiceUri.ToString()))
                    throw new Exception("Web Service Not Found");
     
                XmlTextReader xmlreader = new XmlTextReader(webServiceUri.ToString() + "?wsdl");
     
                ServiceDescriptionImporter descriptionImporter = BuildServiceDescriptionImporter(xmlreader);
     
                return CompileAssembly(descriptionImporter);
            }
     
            private Assembly webServiceAssembly;
            private List<string> services;
        }
    }

    This C# class can be easily used to invoke web service methods:

    WebServiceInvoker invoker = new WebServiceInvoker(new Uri("http://localhost/services/test.php"));
     
    string service = “MyService”;
    string method = “MyMethod”;
     
    string[] args = new string[] { “arg1”, “arg2” };
     
    string result = invoker.InvokeMethod<string>(service, method, args);

    I’ve put together an example application that you can download to evaluate the class. It allows you to invoke methods from a web service entered on the GUI and see the return value. You can download it here.

    I hope you find this useful and if you have any questions or comments be sure to leave them here.

    April 8, 2009   Posted in: Programming

    27 Responses

    1. Joel Chinchilla - April 17, 2009

      Hi, I tried to test your example with a Java Web Service, however I need to pass credentials to Web Service. How can I do it?

    2. Neena - May 7, 2009

      Can I call this code from java

    3. crow - May 10, 2009

      Not that I am aware of.

    4. Sergio - May 14, 2009

      Hi!
      finally a good example on how to call WS dynamically!
      I tryed your code, and got this problem:
      - my WS is as follows:

      public class Service1 : System.Web.Services.WebService
      {
      public Service1()
      {
      InitializeComponent();
      }

      [WebMethod]
      public double Sum(double num1, double num2)
      {
      if ( guid == “”)
      return -1;
      return num1 + num2;
      }
      }

      couldn’t be as simpler as this.
      When i run your code, it does in fact create an assemby whith the correct Service1 classname and the Sum method.
      So everything seems to be ok.
      Problem is when I invoke Sum method it sends an exception saying that “Method ‘Service1.Sum’ not found” on line :
      return (T)type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, obj, args);

      Any clue?

      thanks in advance!

    5. shortcuts - May 18, 2009

      Off-topic Help - Need a secure web proxy to get around my school firewall. Any suggestions?

    6. Jeremy Foster - May 19, 2009

      I have this implemented and working but I have run into a limitation. I can not figure out how to capture multiple returned values in the SOAP response. Do you know of a way to do this?

    7. Sergio - May 19, 2009

      Jeremy,

      what do you mean with “multiple returned values in the SOAP response”. A method only returns 1 value/object. The value itself can have a collection of values/objects. Is this what you meant?
      You may try to transform the returned value to XML. Use this:

      public static string SerializeObjectToXML(T obj)
      {
      try
      {
      string xmlString = null;
      MemoryStream memoryStream = new MemoryStream();
      System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(typeof(T));
      string s = “”;
      XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
      System.Runtime.Serialization.Formatters.Soap.SoapFormatter xsoap = new System.Runtime.Serialization.Formatters.Soap.SoapFormatter();
      xsoap.Serialize(memoryStream, obj);

      xmlString = UTF8ByteArrayToString(memoryStream.ToArray());
      return xmlString;
      }
      catch(Exception ex)
      {
      ex.GetType();
      return string.Empty;
      }
      }

      public static T UnserializeObjectFromXML(string xml)
      {
      System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(typeof(T));
      MemoryStream memoryStream = new MemoryStream(StringToUTF8ByteArray(xml));
      XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
      return (T)xs.Deserialize(memoryStream);
      }

      The Serialize method uses the SoapFormatter class, and in fact serializes the returned object just like what the Webservice call does.

    8. Izzy - May 22, 2009

      Hi, Crows.
      i get this webpage/artical by google.

      i got one question.

      if the input parameter of the method is normal types, like string, int etc, then it is ok.

      right now, I got one webservie, and its input parameter of the function is like this

      string
      string
      int
      int
      int
      int
      int
      int
      int
      int

      there is only one input parameter : TestMessage_input.
      it is a type which is similiar with Struct.

      I’m confused about how to forming such an input parameter in GUI???
      thanks

      your reply will be appreciated and helpful.

    9. Izzy - May 22, 2009

      oh,some infos are missing.they were deleted automatically.

      would you mind telling me your email?
      since i really have some confusions about using this.
      is that ok?

    10. Shaun - May 22, 2009

      Excellent routine! I’m having a problem using this class however. When I call “return results.CompiledAssembly;” I get the following exception:

      Execution permission cannot be acquired.
      Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

      Exception Details: System.Security.Policy.PolicyException: Execution permission cannot be acquired.

      Source Error:

      An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

      Stack Trace:

      [PolicyException: Execution permission cannot be acquired.]
      System.Security.SecurityManager.ResolvePolicy(Evidence evidence, PermissionSet reqdPset, PermissionSet optPset, PermissionSet denyPset, PermissionSet& denied, Boolean checkExecutionPermission) +10236136
      System.Security.SecurityManager.ResolvePolicy(Evidence evidence, PermissionSet reqdPset, PermissionSet optPset, PermissionSet denyPset, PermissionSet& denied, Int32& securitySpecialFlags, Boolean checkExecutionPermission) +97

      [FileLoadException: Could not load file or assembly 'm2wivo6v, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. Failed to grant permission to execute. (Exception from HRESULT: 0x80131418)]
      System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection) +0
      System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) +416
      System.Reflection.Assembly.Load(AssemblyName assemblyRef, Evidence assemblySecurity) +33
      System.CodeDom.Compiler.CompilerResults.get_CompiledAssembly() +105…

      I’m running this code in SharePoint 2007 on Windows Server 2008.

    11. vikram - May 26, 2009

      It’s working fine but application memory increases when web service call are made 2500 times.

      Can we delete the temporary files created while compiling assembly ?

    12. marmri - May 31, 2009

      Hi,
      Thank you for this great example.
      I have the same problem as Sergio - May 14, 2009
      Did you wirte him any suggestion? Please forward it or add it in this article for the whole aucience!
      I have another question: how to do, if you have following situation:

      MyWebReference.GoodbyeWorldWSService myProxy = new MyWebReference.GoodbyeWorldWSService();
      MyWebReference.sayGoodbye request = new MyWebReference.sayGoodbye();
      request.message = “ciao”;
      myProxy.sayGoodbye(request);

      I mean, you have like an object as parameter you first initialize then invoke the service method!

      Please help, is urgent. Thanx a lot in advance

    13. Jeff - June 13, 2009

      Hi As I walk on the code.. i see that the reason why its throwing an exception on this line (T)type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, obj, args); it is maybe because you are calling a method with complex (custom) type and not a generic type. try to have a breakpoint on that line and see the details of the “type” variable.

    14. shail - June 27, 2009

      I am using your example. But its giving me an error on InvokeMember that “The request failed with HTTP status 401: Access Denied” Is there anything special, i will have to do here?

      Thanks,

      Shail …

    15. RadoColo - June 30, 2009

      Beautiful, works like a charm!

    16. usmanbinasif - July 6, 2009

      Dear it is awesome code example of this problem domain, and it is very helpful for me.

      Thanks for sharing such a wonderful peice of code.

      Regards,
      Usman Javed

    17. newbieDotNET - July 7, 2009

      Will it be possible to invoke or set the timeout property? Any help would be greatly appreciated.

      Thanks,
      Jon

    18. David Roys - July 8, 2009

      What an awesome example. Well explained and a great download! Thank you for your time and effort. Unfortunately it doesn’t quite work for me.

      I am also having the problem with authentication that shail posted. I know that when I use my web service by generating the proxy class, I need to use the line:

      ws.UseDefaultCredentials = true in order to be able to user the service.

      I am getting the following error:

      Unable To Load Service: The remote server returned an error: (401) Unauthorized.

      when I click the Load Service button and I can only assume it is because the XMLTextReader is not negotiating my credentials. If I use Internet explorer to hit the URL it will give me the WSDL but I believe this is because it handles the challenge response correctly. Do I need to use another method of getting my WSDL - i.e. not using XMLTextReader?

      Thanks again for such a great post! I learnt a lot.

    19. Mark - July 8, 2009

      Good work, excellent explanation.

      functions that expect strings do work, but functions that expect other types (int, byte[], …) do not work.

      Any idea how to solve this?

      Thanks

      Mark

    20. David Roys - July 10, 2009

      Hi there, OK, I thought I would let other readers know that I managed to sort out my problems with authentication.

      You can read the details on a blog post I made http://www.teachmenav.com/blogs/dave/archive/2009/07/11/using-reflection-to-call-nav-2009-web-services.aspx

      Thanks again for the great post.

    21. Crows Programming » I’m Back - July 15, 2009

      [...] Hey guys, sorry I’ve been away for a bit. I’ll try and respond to your questions, especially those over on the C# dynamic web service page. [...]

    22. MaTZ - July 22, 2009

      Hi,
      I have a problem with http authentication.
      I managed to download WSDL by modifying BuildAssemblyFromWSDL like this:

      System.Net.WebClient client = new System.Net.WebClient();

      if (login.Length > 0)
      {
      client.Credentials = new NetworkCredential(login, password);
      }

      System.IO.Stream stream = client.OpenRead(webServiceUri.ToString() + “?wsdl”);

      XmlTextReader xmlreader = new XmlTextReader(stream);

      ServiceDescriptionImporter descriptionImporter = BuildServiceDescriptionImporter(xmlreader);

      return CompileAssembly(descriptionImporter);

      Now I’m getting exception (error 401) later when I try to invoke web method. Any ideas?

      Thanks for the post.

    23. facebook proxy - July 25, 2009

      Interesting article. Were did you got all the information from… :)

    24. proxies - July 25, 2009

      You are a very smart person! :)

    25. Andrew Brown - August 4, 2009

      Very useful post. I have been using this technique for over a year.

      I am having a problem where the web service request takes longer than 110 seconds to return a response. This causes a timeout error. I have been troubleshooting the timeout issue by changing all configurable timeout settings from the application setting (web.config) and IIS.

      [Error Details]
      System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. —> System.Net.WebException: The operation has timed out.

      I setup a simple test where the web server thread sleeps for 2 mins and 30 seconds.

      Please post if anyone has solved this.

    26. Donald Drake - August 5, 2009

      ““Method ‘Service1.Sum’ not found” ”

      I was able to fix this error by using the following instead.

      MethodInfo method = type.GetMethod(methodName);
      return (T) method.Invoke(instance, args);

      I believe the issue is due to the fact that it is using BindingFlags.InvokeMethod.. I was not able to use any other bindingflag with the type.InvokeMember method.

      Hope this helps.

      Thanks,
      Don

    27. Andrew Brown - August 5, 2009

      I found a solution to the Timeout issue. I had to set the Timeout property of the proxy instance.

      [CODE]
      PropertyInfo propInfo = obj.GetType().GetProperty(”Timeout”);
      propInfo.SetValue(obj, 300000 /* set this to whatever timeout period you want */, null);

      Then invoke the method.

    Posted by tornado
    |
    출처 : http://help.wugnet.com/windows2/Change-group-user-AD-ftopict486801.html

    다른건 볼거 없고,  

    oGroupToRemove.Properties["member"].Remove("CN=" + LoginID); 

    여기처럼 properties["member"] 를 통해 AD Group 의 사용자를 가져올 수 있다.


    --------------------------------------------------------------------------------------------------

    coming "The server is unwilling to process the request. (Exception
    from HRESULT: 0x80072035) trying to remove a user from group". I'm
    giving a code block here:

    //change group membership
    //first remove user from other groups

    ArrayList arlGroups = GetGroups(LoginID, false);
    DirectoryEntry oGroupToRemove = new DirectoryEntry();
    for (int i = 0; i < arlGroups.Count; i++)
    {
    string sGroup = arlGroups.ToString();
    sGroup = sGroup.Substring(0, sGroup.IndexOf(","));
    string[] split = sGroup.Split(new Char[] { '=' });
    sGroup = split[1].ToString();
    oGroupToRemove = oDirEntry.Children.Find("CN=" + sGroup, "group");
    //THE NEXT LINE IS CAUSING THE EXCEPTION
    oGroupToRemove.Properties["member"].Remove("CN=" + LoginID);

    oGroupToRemove.CommitChanges();
    oGroupToRemove.Close();
    }
    //add member to the given group
    DirectoryEntry oGroup = new DirectoryEntry();
    oGroup = oDirEntry.Children.Find("CN=" + GroupName, "group");
    if (oGroup != null)
    {
    oGroup.Invoke("Add", new object[] { oUser.Path.ToString() });
    }
    oGroup.CommitChanges();
    oGroup.Close();

    Please help me as soon as possible.
    Thnx in advance




    Posted by tornado
    |

    출처 : http://msdn.microsoft.com/ko-kr/magazine/cc135979.aspx




    .NET Framework 3.5의 디렉터리 보안 주체 관리
    Joe Kaplan and Ethan Wilansky
    코드 다운로드 위치: DSAccountManagement2008_01.exe (160 KB)
    Browse the Code Online


     

    이 기사는 Visual Studio 2008 시험판 버전을 기준으로 합니다. 여기에 포함된 모든 정보는 변경될 수 있습니다.

    이 기사에서 다루는 내용:
    • System.DirectoryServices.AccountManagement 클래스
    • Active Directory 도메인 서비스
    • AD LDS(Active Directory Lightweight Directory Services)
    • 사용자, 컴퓨터 및 그룹 보안 주체 관리
    이 기사에서 사용하는 기술:
    .NET Framework 3.5, Visual Studio 2008

    디렉터리는 엔터프라이즈 응용 프로그램 개발의 중요한 구성 요소이지만 디렉터리에 대해 정통한 개발자는 드뭅니다. Windows® 플랫폼의 경우 Microsoft는 3가지 기본 디렉터리 플랫폼을 제공하고 있습니다. Active Directory® 도메인 서비스, 모든 Windows 컴퓨터의 로컬 SAM(보안 계정 관리자) 데이터 저장소, 그리고 기존에 ADAM(Active Directory Application Mode)이라고 알려진 AD LDS(Active Directory Lightweight Directory Services)가 그것입니다. 엔터프라이즈 개발자라면 대부분 적어도 SQL 프로그래밍의 기본은 알고 있지만 디렉터리 서비스 프로그래밍 경험이 있는 개발자는 많지 않습니다.
    Microsoft® .NET Framework의 초기 버전에서는 System.DirectoryServices 네임스페이스에 디렉터리 서비스 프로그래밍을 위한 클래스 집합을 제공했습니다. 이러한 클래스는 기존 COM 기반 OS 구성 요소, 특히 ADSI(Active Directory 서비스 인터페이스)에 대한 관리되는 단순한 interop 계층이었습니다. 프로그래밍 모델은 상당히 강력했지만 완전한 ADSI 모델에 비해 일반화되고 단순화된 형태였습니다.
    이어 Microsoft는 .NET Framework 2.0에서 System.DirectoryServices에 기능을 추가하고 System.DirectoryServices.ActiveDirectory와 System.DirectoryServices.Protocols라는 두 가지 새로운 네임스페이스를 제공하기 시작했습니다. 이 기사에서는 두 네임스페이스를 각각 ActiveDirectory 네임스페이스 및 Protocols 네임스페이스라고 하겠습니다. ActiveDirectory 네임스페이스는 서버, 도메인, 포리스트, 스키마, 복제 등 인프라 수준 구성 요소를 강력한 형식으로 관리하는 다양한 새 클래스를 제공합니다. 반면 Protocols 네임스페이스는 LDAP(Lightweight Directory Access Protocol) 프로그래밍을 위한 대체 API를 제공하는 다른 방향을 선택했습니다. 이 네임스페이스는 ADSI COM interop 계층을 전혀 거치지 않고 Windows LDAP 하위 시스템(wldap32.dll)과 직접 작업합니다(그림 1 참조).
    그림 1 Microsoft 디렉터리 서비스 프로그래밍 아키텍처 (더 크게 보려면 이미지를 클릭하십시오.)
    그러나 개발자들은 여전히 사용자 및 그룹과 같은 이전의 보안 주체를 관리하는 데 사용했던 ADSI의 몇 가지 강력한 형식의 인터페이스를 원했습니다. System.DirectoryServices의 일반적인 클래스를 사용해도 이러한 작업을 대부분 수행할 수 있었지만 사용하기가 다소 까다로웠고 난해한 작업이 많았습니다. 이에 .NET Framework 3.5에는 보안 주체 관리를 위해 특별히 설계된 System.DirectoryServices.AccountManagement라는 새로운 네임스페이스가 추가되었습니다. 이 기사에서는 System.DirectoryServices.AccountManagement를 간단히 AccountManagement 네임스페이스로 지칭하도록 하겠습니다.
    이 새로운 네임스페이스에는 3가지 주요 목표가 있습니다. 3가지 디렉터리 플랫폼 전반에 걸친 보안 주체 관리 작업을 단순화하고, 기본 디렉터리에 관계없이 보안 주체 관리 작업이 일관되도록 하며, 주의 사항이나 특수한 상황을 모두 기억하지 않고도 안정적인 작업 결과를 얻을 수 있도록 하는 것이 바로 이러한 목표입니다.
    .NET이 자리 잡기까지 수년을 기다린 끝에 Microsoft는 마침내 ADSI에서 제공되던 이전의 방식을 능가하는 솔루션을 내놓을 수 있었습니다. 이는 .NET 기능을 이용하여 동일한 기능을 구현하는 향상된 API를 제공하면서 AD LDS와 같은 새로운 디렉터리 플랫폼도 효과적으로 지원함에 따라 가능했습니다.

    디렉터리 서비스 프로그래밍 아키텍처
    본론으로 들어가기 전에 이 기사의 코드 다운로드에서 필수 구성 요소를 확인하십시오. 여기서 설명하는 기법을 사용하기 위해서는 이러한 항목이 필요합니다. 준비가 되었으면 본격적으로 시작해 보도록 하겠습니다. 그림 1은 System.DirectoryServices의 전반적인 프로그래밍 아키텍처를 보여 줍니다. AccountManagement 네임스페이스는 ActiveDirectory 네임스페이스와 마찬가지로 System.DirectoryServices 위에 있는 추상화 계층입니다. 그리고 System.DirectoryServices 자체도 ADSI 위에 있는 추상화 계층입니다. 또한 AccountManagement 네임스페이스는 고성능 인증과 같은 몇 가지 기능을 제공하기 위해 Protocols 네임스페이스를 사용합니다. 그림 1의 남색 음영은 디렉터리 서비스 프로그래밍 아키텍처에서 AccountManagement가 사용하는 부분을 나타냅니다.
    그림 2에는 AccountManagement 네임스페이스의 핵심 형식이 나와 있습니다. .NET Framework 2.0에 추가된 네임스페이스와는 달리 AccountManagement는 노출 영역이 상대적으로 작습니다. 열거 및 예외 클래스와 같은 몇 가지 지원 형식을 제외하면 AccountManagement는 크게 강력한 형식의 사용자, 그룹 및 컴퓨터 개체를 나타내는 보안 주체 파생 클래스의 트리, 기본 저장소 연결에 사용되는 PrincipalContext 클래스, 디렉터리에서 개체를 찾는 데 사용되는 PrincipalSearcher 클래스(지원 형식과 함께 제공)의 3가지 구성 요소로 이루어져 있습니다.
    그림 2 System.DirectoryServices.AccountManagement의 핵심 클래스 (더 크게 보려면 이미지를 클릭하십시오.)
    내부적으로 AccountManagement 네임스페이스는 지원되는 3가지 디렉터리 플랫폼에서 작동할 수 있도록 공급자 디자인 패턴을 사용합니다. 결과적으로 기본 디렉터리 저장소에 관계없이 다양한 Principal 클래스의 멤버가 비슷하게 작동합니다. 이는 단순성과 일관성을 유지하는 데 핵심이 되는 설계입니다.

    컨텍스트 설정
    대상 디렉터리에 연결하고 디렉터리 작업을 수행하는 데 필요한 자격 증명을 지정하는 데는 PrincipalContext 클래스를 사용합니다. 이러한 방식은 ActiveDirectory 네임스페이스에서 DirectoryContext 클래스와의 컨텍스트를 설정하는 방법과 비슷합니다.
    PrincipalContext 생성자에는 컨텍스트 설정에 정확히 개발자가 원하는 대로 옵션을 제공하기 위한 다양한 오버로드가 있습니다. System.DirectoryServices의 DirectoryEntry 클래스를 작업에 사용하다 보면 많은 PrincipalContext 옵션이 익숙하게 느껴질 것입니다. 그러나 ContextType, name 및 container의 3가지 PrincipalContext 옵션은 DirectoryEntry 클래스에 사용하는 입력 매개 변수보다 훨씬 한정적입니다. 이렇게 옵션의 용도를 구체화함으로써 항상 올바른 입력 매개 변수 조합을 사용하도록 한 것입니다. System.DirectoryServices 및 ADSI에서 PrincipalContext 생성자의 이 3가지 매개 변수는 경로라는 단일 문자열로 조합됩니다. 이러한 구성 요소를 분리함으로써 경로의 각 부분이 어떠한 기능을 하는지를 쉽게 이해할 수 있습니다.
    ContextType 열거형을 사용하여 대상 디렉터리 유형을 Domain(Active Directory 도메인 서비스의 경우), ApplicationDirectory(AD LDS의 경우) 또는 Machine(로컬 SAM 데이터베이스의 경우)으로 지정합니다. 반면 System.DirectoryServices를 사용할 때는 경로 문자열의 공급자 구성 요소(일반적으로 "LDAP" 또는 "WinNT")를 사용하여 대상 저장소를 지정합니다. 그러면 ADSI가 내부적으로 이 값을 읽어 적절한 공급자를 로드합니다.
    AccountManagement 네임스페이스의 클래스는 프레임워크에서 지원되는 공급자만 사용하도록 하여 이 작업을 쉽고 일관성 있게 만들 뿐 아니라, ADSI 및 System.DirectoryServices 프로그래밍에서 자주 발생하는 공급자 입력 실수나 잘못된 대/소문자 사용에 따른 성가신 문제도 방지합니다. 바꾸어 말하면 AccountManagement는 IIS 및 Novell Directory Services와 같이 일반적으로 잘 사용되지 않는 ADSI 공급자는 지원하지 않습니다.
    연결할 특정 디렉터리의 이름을 제공하려면 PrincipalContext 생성자에 name 매개 변수를 사용해야 합니다. 이 매개 변수의 값은 특정 서버, 시스템 또는 도메인의 이름이 될 수 있습니다. 이 매개 변수가 null이면 AccountManagement가 현재 보안 컨텍스트를 기준으로 연결을 위한 기본 시스템 또는 도메인을 결정합니다. 그러나 AD LDS 저장소에 연결하려면 name 매개 변수의 값을 지정해야 합니다.
    container 매개 변수는 컨텍스트를 설정할 디렉터리의 대상 위치를 지정하는 데 사용됩니다. SAM 데이터베이스는 계층 구조가 아니므로 Machine ContextType을 사용할 때는 이 매개 변수를 지정하지 마십시오. 반대로 AD LDS는 디렉터리 루트 개체를 유추할 때 사용할 defaultNamingContext 특성을 게시하지 않기 때문에 ApplicationDirectory를 사용할 때는 반드시 이 매개 변수에 값을 제공해야 합니다. Domain ContextType의 경우 이 매개 변수를 선택적으로 지정할 수 있으며 지정하지 않으면 AccountManagement는 defaultNamingContext를 사용합니다.
    필요에 따라 추가 매개 변수(username, password 및 ContextOptions 열거형)를 사용하여 일반 텍스트 자격 증명을 제공하고 사용할 여러 가지 연결 보안 옵션을 지정할 수 있습니다.
    모든 디렉터리는 Windows 협상 인증 방법을 지원합니다. Machine 저장소에 대해 옵션을 지정하지 않으면 Windows 협상 인증이 사용됩니다. 그러나 Domain 및 ApplicationDirectory의 경우에는 서명 및 밀봉을 모두 포함한 Windows 협상이 기본으로 사용됩니다.
    Active Directory 도메인 서비스와 AD LDS는 LDAP 단순 바인딩도 지원합니다. 일반적으로 Active Directory 도메인 서비스에는 LDAP 단순 바인딩을 사용하지 않겠지만 AD LDS의 경우 AD LDS 저장소의 사용자가 보안 주체 작업을 수행하도록 하려면 LDAP 단순 바인딩이 필요할 수 있습니다.
    username 또는 password 매개 변수를 null로 지정하면 AccountManagement가 현재 Windows 보안 컨텍스트를 사용합니다. 자격 증명을 지정할 때 지원되는 사용자 이름 형식으로는 SamAccountName, UserPrincipalName 및 NT4Name이 있습니다. 그림 3은 컨텍스트를 설정하는 3가지 방법을 보여 줍니다.
    // TechWriters OU를 가르키고 기본 자격 증명을 사용하는
    // Fabrikam이라는 도메인에 대한 컨텍스트 만들기
    PrincipalContext domainContext = new PrincipalContext( 
      ContextType.Domain,"Fabrikam","ou=TechWriters,dc=fabrikam,dc=com");
    
    // 현재 보안 컨텍스트를 사용하여
    // 현재 시스템 SAM 저장소에 대한 컨텍스트 만들기
    PrincipalContext machineContext = new PrincipalContext(
      ContextType.Machine);
    
    //AD LDS 저장소의 사용자 자격 증명 및
    // SSL 암호화를 사용하여 파티션 루트를 가리키는
    // AD LDS 저장소에 대한 컨텍스트 만들기
    PrincipalContext ldsContext = new PrincipalContext(
        ContextType.ApplicationDirectory, "sea-dc-02.fabrikam.com:50001", 
        "ou=ADAM Users,o=microsoft,c=us", 
        ContextOptions.SecureSocketLayer | ContextOptions.SimpleBind, 
        "CN=administrator,OU=ADAM Users,O=Microsoft,C=US ", "pass@1w0rd01");
    
    
    AccountManagement PrincipalContext 및 System.DirectoryServices DirectoryEntry 클래스 사이에는 바인딩 작업 시에 작지만 중요한 차이가 있습니다. PrincipalContext는 개체 생성 시 기본 디렉터리에 연결하고 바인딩하지만 DirectoryEntry는 연결이 필요한 다른 작업을 수행할 때까지 바인딩하지 않습니다. 결과적으로 PrincipalContext를 사용하면 연결이 디렉터리에 성공적으로 바인딩되었는지 바로 알 수 있습니다.

    사용자 계정 만들기
    이제 AccountManagement가 PrincipalContext를 사용하여 컨테이너에 연결하고 바인딩하는 과정은 충분히 배웠습니다. 다음은 일반적인 DirectoryServices 작업인 사용자 계정 만들기에 대해 알아보겠습니다. 이 과정에서 각 코드 샘플은 하나의 필수 특성에 값을 할당하고, 두 개의 선택적인 특성을 추가하며, 암호를 설정하고, 사용자 계정을 활성화한 다음, 변경 내용을 디렉터리로 커밋합니다.
    여기에서는 그림 3의 예 1에서 소개한 domainContext 변수를 사용하여 새 UserPrincipal을 만듭니다.
    // 사용자 보안 주체 개체 만들기
    UserPrincipal user = new UserPrincipal(domainContext,
         "User1Acct", "pass@1w0rd01", true);
    
    // 사용자 보안 주체에 일부 속성 할당
    user.GivenName = "User";
    user.Surname = "One";
    
    // 다음 로그인할 때 암호를 변경하도록 함
    user.ExpirePasswordNow();
    
    // 사용자를 디렉터리에 저장
    user.Save();
    (참고: 프로그래머 주석은 예제 프로그램 파일에는 영문으로 제공되며 기사에는 이해를 돕기 위해 번역문으로 제공됩니다.)
    
    domainContext는 작업 수행에 사용되는 디렉터리에 대한 연결과 보안 컨텍스트를 설정합니다. 그리고 한 줄의 코드로 새 user 개체를 만들고 암호를 설정하여 활성화합니다. 그런 다음 GivenName 및 Surname 속성을 사용하여 기본 저장소의 해당 디렉터리 특성을 설정합니다. 기본 디렉터리 저장소에 개체를 저장하기 전에 암호가 만료되어 사용자가 첫 번째 로그온 시에 암호를 변경하도록 합니다.
    그림 4에는 이에 해당하는 System.DirectoryServices에서 사용자 계정을 만드는 단계가 나와 있습니다. 첫 번째 코드 줄의 container 변수는 연결에 경로를 사용하는 DirectoryEntry 클래스 개체입니다. 이 경로는 공급자, 도메인 및 컨테이너(TechWriters OU)를 지정하고 현재 사용자의 보안 컨텍스트를 사용하여 연결합니다. container 변수는 사용자 보안 주체를 만드는 이전 예에서 domainContext와 비슷합니다.
        DirectoryEntry container =
        new DirectoryEntry("LDAP://ou=TechWriters,dc=fabrikam,dc=com");
    // 컨테이너에 사용자 디렉터리 엔트리 만들기
    DirectoryEntry newUser = container.Children.Add("cn=user1Acct", "user");
    
    // samAccountName 필수 특성 추가
    newUser.Properties["sAMAccountName"].Value = "User1Acct";
    
    // 선택적 특성 추가
    newUser.Properties["givenName"].Value = "User";
    newUser.Properties["sn"].Value = "One";
    
    // 디렉터리에 저장
    newUser.CommitChanges();
    
    // Invoke 메서드 및 IadsUser.SetPassword를 사용하여
    // 사용자 계정에 대한 암호 설정
    newUser.Invoke("SetPassword", new object[] { "pAssw0rdO1" });
    
    // 다음 로그인할 때 암호를 변경하도록 지정
    newUser.Properties["pwdLastSet"].Value = 0;
    
    // 사용자 계정을 활성화
    // newUser.InvokeSet("AccountDisabled", new object[]{false});
    // 또는 ADS_UF_NORMAL_ACCOUNT (512)를 사용하여 효과적으로
    // 비활성화된 비트를 설정 해제
    newUser.Properties["userAccountControl"].Value = 512;
    
    // 디렉터리에 저장
    newUser.CommitChanges();
       
    이 코드는 AccountManagement 예보다 훨씬 길고 복잡합니다. 따라서 디렉터리의 기본 데이터 모델에 대해 더 많이 알아야 하고, 초기 암호를 설정하거나 disabled 플래그를 해제하여 user 개체를 활성화하는 등, 특정 계정 관리 기능을 처리하는 방법도 알아야 합니다. 이는 리플렉션 기반 Invoke 및 InvokeSet 메서드를 사용하여 기본 ADSI COM 인터페이스 멤버를 호출해야 하는 경우 까다로울 수 있습니다. 많은 개발자가 디렉터리 서비스 프로그래밍을 어렵게 생각하는 이유도 바로 이 때문입니다.
    AD LDS 또는 로컬 SAM 데이터베이스에서 같은 사용자 계정을 만들 때는 차이가 더 커집니다. AD LDS와 Active Directory 도메인 서비스는 사용자 계정을 활성화하는 데 서로 다른 메커니즘(Active Directory 도메인 서비스는 userAccountControl 특성을 사용하지만 AD LDS는 msds-userAccountDisabled 특성을 사용)을 사용하며, SAM 저장소의 경우 ADSI 인터페이스 멤버를 호출해야 합니다. 이렇게 3가지 디렉터리 저장소의 계정 관리 기능이 서로 다르다는 점은 AccountManagement 네임스페이스 설계에 있어서 해결되어야 할 중요한 과제였습니다. 그러나 이제 UserPrincipal 개체를 만드는 데 사용되는 PrincipalContext를 변경함으로써 간단히 디렉터리 저장소 사이를 전환하고 일관성 있는 한 가지 계정 관리 기능 집합을 얻을 수 있게 되었습니다.
    .NET Framework 2.0에 도입된 Protocols 네임스페이스는 이러한 문제 해결과는 전혀 관련이 없었습니다. 단지 시스템 수준 LDAP 프로그래머에게 LDAP 기반 응용 프로그램 작성에 사용할 수 있는 보다 강력하고 유연한 API를 제공하기 위한 것이었습니다. System.DirectoryServices보다 LDAP 모델에 대한 추상화 수준이 낮기 때문에 다양한 디렉터리 간의 차이를 줄이는 데는 효과가 없었습니다. 게다가 LDAP 디렉터리가 아닌 로컬 SAM 데이터베이스에는 사용할 수 없었습니다. 이 기사와 함께 제공되는 온라인 코드 샘플에는 그림 3 다음에 나오는 AccountManager 예와 비슷한 샘플이 포함되어 있습니다. 이 샘플은 해당 예와 동일한 작업을 수행하지만 코드 줄 수는 3배에 달합니다.
    그림 5의 PrincipalContext는 SAM 저장소를 대상으로 지정하기 위해 시스템을 참조하는 ContextType 열거형을 보여 줍니다. 다음 매개 변수는 실제 시스템을 이름 또는 IP 주소를 기준으로 대상으로 지정하고 마지막 두 값은 계정 생성을 수행할 수 있는 계정의 자격 증명을 제공합니다.
    PrincipalContext principalContext = new PrincipalContext(
        ContextType.Machine. "computer01", "adminUser", "adminPassword");
       
    UserPrincipal user = new UserPrincipal(principalContext,
        "User1Acct", "pass@1w0rd01", true);
    
    //다른 저장소에 액세스할 때는 특성에 차이가 있으며
    //IntelliSense에 나타나는 특성은 기반 저장소에서
    //파생되지 않음을 유의하십시오.
    user.Name = "User One";
    user.Description = "User One";
    user.ExpirePasswordNow();
    user.Save();
    
    Active Directory 도메인 서비스와는 달리 SAM 저장소에는 givenName, sn 또는 displayName 특성이 없기 때문에 Name 및 Description 속성을 설정해야 합니다. AccountManagement는 3가지 디렉터리 저장소 모두에 일관성 있는 환경을 제공하기는 하지만 기본 모델 자체에 차이가 있습니다. 그러나 기본 저장소에서 제공되지 않는 특성을 가져오려고 하면 InvalidOperationException이 발생합니다.
    그림 3과 그림 5는 사용자 계정 생성 방법을 보여 주는 두 가지 예입니다. 이 예에서는 저장소에 관계없이 작업을 수행하는 AccountManagement 네임스페이스의 일관성 있는 고유 프로그래밍 모델을 보여 줍니다. 그림 6의 PrincipalContext는 AD LDS 저장소를 대상으로 지정하기 위해 그림 3과 같이 ContextType.ApplicationDirectory를 사용합니다. 다음 매개 변수는 AD LDS 서버를 보여 줍니다. 이 예에서 sea-dc-02.fabrikam.com은 AD LDS 인스턴스를 호스트하는 서버의 FQDN(정규화된 도메인 이름)이며 인스턴스는 50001 포트에서 SSL 통신을 수신합니다. 코드 다운로드에서는 이와 달리 50000 포트를 통한 비 SSL 통신을 사용합니다. 안전하지는 않지만 테스트 용도로는 문제가 없을 것입니다.
    PrincipalContext principalContext = new PrincipalContext(
        ContextType.ApplicationDirectory,
        "sea-dc-02.fabrikam.com:50001",
        "ou=ADAM Users,o=microsoft,c=us",
        ContextOptions.SecureSocketLayer | ContextOptions.SimpleBind,
        "CN=administrator,OU=ADAM Users,O=Microsoft,C=US",
        "P@55w0rd0987");
    
    UserPrincipal user = new UserPrincipal(principalContext,
        "User1Acct", "pass@1w0rd01", true);
    
    user.GivenName = "User";
    user.Surname = "One";
    
    user.Save();
    
    
    다음 매개 변수는 CRUD(Create/Read/Update/Delete) 작업을 수행할 컨테이너를 지정합니다. 이 예에서는 CRUD 작업 수행을 위해 AD LDS에 저장된 사용자를 지정하므로 LDAP 단순 바인딩을 사용해야 하며 이는 보안을 위해 SSL과 결합됩니다. AD LDS는 기본적으로 보안 DIGEST 인증을 지원하지만 ADSI는 이를 지원하지 않습니다. 이번에도 역시 이 예는 PrincipalContext가 크게 다르지만 이전의 두 예와 사실상 동일합니다.
    AccountManagement 네임스페이스는 암호 만료, 계정 잠금 해제와 같은 포괄적인 계정 관리 기능 집합을 제공합니다. 이 기사에서 이러한 기능을 모두 소개할 수는 없지만 이러한 기능은 디렉터리 저장소 유형에 관계없이 일관적이고 안정적으로 작동하며 이를 구현하기 위한 복잡한 과정이 없어졌습니다.

    그룹 및 컴퓨터 만들기
    사용자 계정을 만드는 작업은 DirectoryServices 저장소에 관계없이 간단하고 일관성 있게 수행된다는 것을 살펴보았습니다. 이러한 일관성은 지원되는 다른 두 가지 DirectoryServices 개체인 group 및 computer를 만드는 데도 마찬가지로 유지됩니다. UserPrincipal 클래스와 마찬가지로 GroupPrincipal 및 ComputerPrincipal 클래스도 Principal 추상 클래스에서 상속하고 비슷하게 작동합니다. 예를 들어 다음 코드를 사용하면 Active Directory 도메인 서비스, AD LDS 또는 SAM 계정 데이터베이스에 Group01이라는 그룹을 만들 수 있습니다.
    GroupPrincipal group = new GroupPrincipal(principalContext,
        "Group01");
    group.Save();
    
    각 경우의 차이점은 서로 다른 저장소와의 컨텍스트를 설정하는 PrincipalContext 클래스에 포함됩니다. computer 개체를 생성하는 데 사용되는 코드에서도 컨텍스트를 사용하여 principal 개체를 생성한 다음 보안 주체 컨텍스트의 대상에 개체를 저장하는 비슷한 패턴을 따릅니다.
    ComputerPrincipal computer = new ComputerPrincipal(domainContext);
    computer.DisplayName = "Computer1";
    computer.SamAccountName = "Computer1$";
    computer.Enabled = true;
    computer.SetPassword("p@ssw0rd01");
    computer.Save();
    
    이번에도 AccountManagement는 지원되는 모든 ID 저장소의 상호 작용 모델을 일관성 있게 유지합니다. 이 샘플은 도메인에 가입할 수 있는 computer 개체(sAMAccountName 특성 끝에 $ 필요)를 만드는 구문으로도 사용할 수 있지만 여기에서는 표시 이름 및 일반 이름을 설정하므로 $를 포함하지 않습니다. SAM 데이터베이스와 AD LDS에는 computer 클래스가 없기 때문에 AccountManagement는 도메인 기반 PrincipalContext 내에서만 이러한 유형의 개체를 만들도록 허용합니다. 또한 Active Directory 도메인 서비스에만 글로벌, 유니버설 및 도메인 로컬의 다양한 그룹 범위가 있으며 보안 및 메일 그룹이 모두 포함되어 있습니다. 따라서 GroupPrincipal 클래스는 필요에 따라 이러한 값을 설정할 수 있도록 null 허용 속성을 제공합니다.

    그룹 멤버 자격 관리
    AccountManagement 네임스페이스는 그룹 멤버 자격 관리도 간소화해 줍니다. AccountManagement가 개발되기 전에는 그룹 관리 방식에 있어서 저장소 유형별로 고유한 부분이 많았고 당연히 일관성도 없었습니다. 또한 구성원이 많은 그룹을 프로그래밍 방식으로 관리하기도 어려웠습니다. 게다가 SAM 그룹 멤버 자격을 관리하는 데는 COM interop을 사용하고 Active Directory 도메인 서비스 및 AD LDS 그룹을 관리하는 데는 LDAP 특성을 사용해야 했습니다. 그러나 이제 저장소에 관계없이 GroupPrincipal 클래스의 Members 속성을 사용하여 그룹의 멤버 자격을 열거하고 구성원을 관리할 수 있게 되었습니다.
    사용자가 속한 모든 그룹을 가져오는 작업도 간단해 보이지만 실제로는 구현하기가 까다롭습니다. AccountManagement는 이를 위해 사용할 수 있는 몇 가지 메서드를 제공합니다. Principal 기본 클래스에는 GetGroups 메서드 및 IsMemberOf 메서드가 두 개씩 포함되어 있습니다. 이 메서드는 Principal 형식의 그룹 멤버 자격을 가져오고 해당 보안 주체가 그룹의 구성원인지 확인합니다. 또한 UserPrincipal은 모든 UserPrincipal 형식의 완전히 확장된 보안 그룹 멤버 자격을 반환하는 특수한 GetAuthorizationGroups 메서드도 제공합니다. 그림 7은 GetAuthorizationGroups 메서드 사용 방법을 보여 줍니다.
    string userName = "user1Acct";
    
    // ID 저장소에서 사용자 찾기
    UserPrincipal user =
        UserPrincipal.FindByIdentity(
            adPrincipalContext, 
            userName);
    
    // 사용자 보안 주체에 대한 그룹을 얻고
    // 결과를 PrincipalSearchResult object에 저장
    PrincipalSearchResult<Principal> results = 
        user.GetAuthorizationGroups();
    
    // 사용자가 속할 그룹의 이름을 표시
    Console.WriteLine("groups to which {0} belongs:", userName);
    foreach (Principal result in results)
    {
        Console.WriteLine("name: {0}", result.Name);
    }
    
    
    트러스트된 도메인 또는 외부 보안 주체 간에 그룹 멤버 자격을 확장하는 작업도 AccountManagement에 의해 간소화되었습니다. 이 작업은 Principal 클래스의 GetGroups(PrincipalContext) 메서드가 담당합니다.

    자체 찾기
    디렉터리에서 개체를 찾는 것도 프로그래머가 어려움을 겪는 작업 중 하나입니다. 개발자가 일상적으로 다루는 구문에 비해 LDAP가 특별히 복잡한 쿼리 언어라고 할 수는 없지만 생소한 것이 사실입니다. LDAP에 대해 기초 지식이 있더라도 이를 사용하여 일반적인 작업을 수행하는 방법을 알아내기는 쉽지 않습니다.
    이번에도 AccountManagement는 개체를 찾는 작업을 간단하게 처리할 수 있도록 FindByIdentity 메서드를 제공합니다. 이 메서드는 UserPrincipal, GroupPrincipal 및 ComputerPrincipal 클래스가 상속하는 Principal 추상 클래스에 포함되어 있습니다. 따라서 이러한 Principal 형식 중 하나를 검색해야 할 때마다 FindByIdentity를 사용하면 됩니다.
    FindByIdentity에는 PrincipalContext 및 찾을 값을 받는 두 개의 오버로드가 포함되어 있습니다. 값으로는 SamAccountName, Name, UserPrincipalName, DistinguishedName, Sid 또는 Guid의 지원되는 ID 유형을 사용할 수 있습니다. 두 번째 오버로드의 경우에도 값으로 지정할 ID 유형을 명시적으로 정의할 수 있습니다.
    좀 더 간단한 오버로드를 먼저 살펴보면, 이전 예에서 만든 사용자 계정을 FindByIdentity를 사용하여 반환하는 코드는 다음과 같습니다.
    UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, "user1Acct");
    
    컨텍스트가 설정되면(principalContext 개체에 저장) FindByIdentity 메서드를 사용하여 principal 개체(이 예의 경우 UserPrincipal)를 검색할 수 있습니다. 지원되는 ID 저장소에 대한 컨텍스트를 설정한 이후에는 ID를 검색하는 코드는 항상 동일합니다.
    두 번째 FindByIdentity 생성자는 명시적으로 ID 형식을 지정할 수 있도록 해 줍니다. 이 생성자를 사용할 때는 값의 형식이 지정할 ID 유형과 일치하도록 해야 합니다. 예를 들어 다음 코드는 디렉터리의 지정한 위치에 해당 개체가 있는 경우 고유 이름을 사용하여 UserPrincipal을 반환합니다.
    UserPrincipal user = UserPrincipal.FindByIdentity(
        adPrincipalContext, 
       IdentityType.DistinguishedName,
       "CN=User1Acct,OU=TechWriters,DC=FABRIKAM,DC=COM");
    
    반면에 다음 코드는 IdentityType 열거형이 DistinguishedName 형식을 지정하지만 값은 이 형식이 아니므로 UserPrincipal을 반환하지 않습니다.
    UserPrincipal user = UserPrincipal.FindByIdentity(
        adPrincipalContext,
       IdentityType.DistinguishedName,
       "user1Acct");
    
    형식은 매우 중요합니다. 예를 들어 GUID 또는 SID IdentityType을 사용할 때는 각각 표준 COM GUID 형식 및 SDDL(Security Descriptor Description Language) 형식의 값을 사용해야 합니다. 이 기사의 코드 다운로드에는 올바른 형식을 보여 주는 두 가지 메서드(FindByIdentityGuid 및 FindByIdentitySid)가 포함되어 있습니다. 사용자의 디렉터리 저장소에서 일치하는 결과를 찾으려면 이러한 메서드의 GUID 또는 SID 값을 변경해야 합니다. 잠시 후 살펴보겠지만 PrincipalSearcher 클래스를 사용하면 이러한 형식을 쉽게 얻을 수 있습니다.
    이제 Principal 개체를 찾아 바인딩했으므로 이에 대한 작업을 수행하기는 어렵지 않습니다. 예를 들어 다음과 같이 그룹에 사용자를 추가할 수 있습니다.
    // 사용자 보안 주체 얻기
    UserPrincipal user = 
        UserPrincipal.FindByIdentity(adPrincipalContext, "User1Acct");
    // 그룹 보안 주체 얻기
    GroupPrincipal group = 
        GroupPrincipal.FindByIdentity(adPrincipalContext, "Administrators");
    // 사용자 추가
    group.Members.Add(user);
    // 변경 내용을 디렉터리에 저장
    group.Save();
    
    여기에서는 사용자를 찾은 다음 그룹을 찾는 데 FindByIdentity 메서드를 사용했습니다. 이러한 보안 주체 개체를 얻은 다음에는 그룹 Members 속성의 Add 메서드를 호출하여 그룹에 사용자 보안 주체를 추가합니다. 마지막으로 그룹의 Save 메서드를 호출하여 변경 내용을 디렉터리에 저장합니다.

    일치하는 결과 찾기
    강력한 예제에 의한 쿼리(QBE) 기능과 PrincipalSearcher 클래스를 사용하여 정의된 기준에 따라 개체를 찾을 수도 있습니다. QBE와 PrincipalSearcher 클래스에 대해서는 잠시 후에 자세히 설명하기로 하고, 간단한 검색 예를 먼저 살펴보겠습니다. 그림 8은 name/cn 접두사 "user"로 시작하는 비활성화된 사용자 계정을 모두 찾는 방법을 보여 줍니다.
    // 검색될 대상을 설명하기 위한 보안 주체 개체 표현 만들기
    UserPrincipal user = new UserPrincipal(adPrincipalContext);
    
    // 검색의 속성 정의(와일드카드 사용 가능)
    user.Enabled = false;
    user.Name = "user*";
    
    // 검색 작업을 실행하기 위한 보안 주체 검색기 만들기
    PrincipalSearcher pS = new PrincipalSearcher();
    
    // 만든 보안 주체 개체에 쿼리 필터 속성 할당
    // PrincipalSearcher 생성자에서 사용자 보안 주체를 전달할 수도 있음
    pS.QueryFilter = user;
    
    // 쿼리 실행
    PrincipalSearchResult<Principal> results = pS.FindAll();
    
    Console.WriteLine("Disabled accounts starting with a name of 'user':");
    foreach (Principal result in results)
    {
        Console.WriteLine("name: {0}", result.Name);
    }
    
    
    PrincipalContext 변수인 adPrincipalContext는 Active Directory 도메인을 가리키지만 AD LDS 응용 프로그램 파티션도 마찬가지로 간단하게 가리킬 수 있습니다. 컨텍스트를 설정한 다음 코드에서는 새로운 UserPrincipal 개체를 만듭니다. 이는 검색 작업 대상이 되는 보안 주체의 메모리 내 표현입니다. 이 보안 주체를 만든 후에는 검색 결과를 제한하는 속성을 설정합니다. 다음 두 줄의 코드는 개발자가 설정할 수 있는 몇 가지 제한을 보여 줍니다. 이 코드는 사용자 이름이 특정 값으로 시작하는 비활성화된 모든 사용자를 지정합니다. Name 특성에 대한 속성 값에는 와일드카드를 사용할 수 있습니다.
    LDAP 언어에서 검색 필터를 설정하는 데 익숙한 개발자라면 QBE가 왜 독창적이고 직관적인 대안인지 금방 알 수 있을 것입니다. QBE를 사용하여 쿼리 작업에 사용할 예 개체를 설정합니다. 그림 8에서 생성한 QBE 개체와 동등한 필터를 설정하는 다음 LDAP 언어와 비교해 보면 QBE가 일반적인 DirectoryServices 검색 언어에 비해 간단하다는 사실을 확인할 수 있습니다.
    (&(objectCategory=person)(objectClass=user)(name=user*)(userAccount
    Control:1.2.840.113556.1.4.803:=2))
    
    이 코드에서 보듯이 LDAP 언어는 훨씬 복잡할 뿐만 아니라 Active Directory LDS 사용자 스키마에는 LDAP 언어에서 사용되는 userAccountControl 특성 대신 msDS-UserAccountDisabled 특성이 사용되므로 AD LDS에 대해서는 작동하지 않습니다. 이러한 차이도 역시 AccountManagement에 의해 내부적으로 처리됩니다.
    그림 8에 나와 있는 QBE 개체를 설정하고 나면 PrincipalSearcher 개체를 만들고 코드 이전 부분에서 Principal 개체에 의해 생성된 QueryFilter 속성을 할당합니다. 이렇게 QueryFilter 속성을 설정하는 대신 PrincipalSearcher 생성자에 사용자 보안 주체를 전달할 수도 있습니다. 그런 다음 PrincipalSearcher의 FindAll 메서드를 호출하고 반환되는 결과를 PrincipalSearchResult 일반 목록에 할당하여 쿼리를 실행합니다. PrincipalSearchResult 목록은 반환되는 Principal 개체를 저장합니다. 마지막으로 보안 주체 목록을 열거하고 반환된 각 보안 주체의 Name 특성을 표시합니다.
    참조 특성에는 QBE를 사용할 수 없습니다. 즉, QBE 개체에서 소유하지 않는 특성은 개체의 메모리 내 표현을 구성하는 데 사용할 수 없습니다.
    foreach 루프를 사용하면 비활성화된 사용자 계정을 활성화하거나 삭제하는 등, 훨씬 다양한 작업이 가능합니다. 읽기 작업만 원하는 경우 다른 ID 저장소를 가리키려면 반환할 특성이 해당 저장소에 있어야 한다는 점에 주의해야 합니다. 예를 들어 AD LDS 사용자에는 sAMAccountName 특성이 없으므로 이 특성을 결과로 반환할 수 없습니다.

    까다로운 검색 작업의 단순화
    강력한 FindBy 메서드를 PrincipalSearchResult 클래스와 함께 사용하면 다른 방법으로는 검색이 어려운 사용자 및 컴퓨터 보안 주체에 대한 정보를 검색할 수 있습니다. 그림 9는 오늘 암호를 변경한 각 사용자의 이름을 검색하는 방법을 보여 줍니다. 이 예에서는 FindByPasswordSetTime 메서드와 PrincipalSearchResult 클래스를 사용합니다. 기본 pwdLastSet 특성은 디렉터리에 큰 정수로 저장되기 때문에 AccountManagement를 사용하지 않으면 작업이 복잡해집니다.
    // 오늘 날짜 얻기
    DateTime dt = DateTime.Today;
    
    // 쿼리 실행
    PrincipalSearchResult<Principal> results = 
        UserPrincipal.FindByPasswordSetTime(
            adPrincipalContext, 
            dt, 
            MatchType.GreaterThanOrEquals); 
    
    Console.WriteLine("users whose password was set on {0}", 
        dt.ToShortDateString());
    foreach (Principal result in results)
    {
        Console.WriteLine("name: {0}", result.Name);
    }
    
    
    이 기사의 코드 다운로드에는 다른 FindBy 메서드 사용 예가 포함되어 있습니다. 이러한 다른 예도 모두 작동 방식은 그림 9와 비슷합니다.
    FindBy 메서드는 검색하기 어려운 정보를 쉽게 찾는 유용한 도구지만 QBE 기능을 사용하여 결과를 추가로 필터링해야 하는 경우에는 적합하지 않습니다. 관련 특성이 읽기 전용이기 때문에 QBE 개체에서 특성을 설정할 수 없는 것은 물론 QBE가 참조하는 개체의 사용자도 설정할 수 없습니다. QBE를 사용하려면 예 principal 개체에 해당하는 읽기 전용 속성 및 AdvancedSearchFilter 속성을 함께 사용해야 합니다. 이에 대해서는 나중에 설명하도록 하겠습니다. 그림 10에서는 다른 FindBy 메서드를 나열하고 검색에 FindBy 메서드 대신 사용할 수 있는 해당하는 읽기 전용 속성을 보여 줍니다.

    메서드 이름 읽기 전용 속성 설명
    FindByLogonTime LastLogonTime 지정한 시간 내에 로그온한 계정
    FindByExpirationTime AccountExpirationDate 지정한 시간 내에 만료된 계정
    FindByPasswordSetTime LastPasswordSetTime 지정한 시간 내에 암호가 설정된 계정
    FindByLockoutTime AccountLockoutTime 지정한 시간 내에 잠긴 계정
    FindByBadPasswordAttempt LastBadPasswordAttempt 지정한 시간 내에 잘못된 암호가 입력된 계정
    해당 메서드 없음 BadLogonCount 지정한 횟수만큼 로그온을 시도했지만 로그온에 실패한 계정
    QBE를 구성할 때 읽기 전용 속성의 값은 설정할 수 없습니다. 그렇다면 검색 작업에서 속성은 어떻게 처리해야 할까요? 결과 집합을 열거하는 동안 결과 집합을 검색하고 읽기 전용 속성을 사용하여 조건 테스트를 수행할 수 있습니다. 그러나 크기가 커질 수 있는 결과 집합에는 이러한 방법이 적합하지 않습니다. 읽기 전용 속성으로 필터링되지 않은 결과를 먼저 검색하고 반환된 결과 집합을 읽기 전용 속성으로 필터링해야 하기 때문입니다. 코드 다운로드의 PrincipalSearchEx6v2 메서드는 이러한 비효율적인 방식을 보여 줍니다.
    디렉터리 서비스 팀에서는 AuthenticablePrincipal 클래스에 AdvancedSearchFilter 속성을 추가함으로써 QBE의 이러한 한계를 해결했습니다. AdvancedSearchFilter를 사용하면 읽기 전용 속성을 기반으로 검색을 수행한 다음 QBE 메커니즘을 사용하여 설정 가능한 다른 속성과 결합할 수 있습니다. 그림 11은 UserPrincipal 클래스의 LastBadPasswordAttempt 읽기 전용 속성을 사용하여 오늘 잘못된 암호로 액세스를 시도한 사용자를 반환하는 방법을 보여 줍니다.
    DateTime dt = DateTime.Today;
    
    // 검색될 대상을 설명하기 위한 보안 주체 개체 표현 만들기
    UserPrincipal user = new UserPrincipal(adPrincipalContext);
    
    user.Enabled = true;
    
    // 검색의 속성 정의(와일드카드 사용 가능)
    user.Name = "*";
    
    // 쿼리 필터에 LastBadPasswordAttempt >= Today 추가
    user.AdvancedSearchFilter.LastBadPasswordAttempt
        (dt, MatchType.GreaterThanOrEquals);
    
    // 검색 작업을 실행하기 위한 보안 주체 검색기를 만들고
    // 쿼리 필터로 QBE 사용자 보안 주체를 할당
    PrincipalSearcher pS = new PrincipalSearcher(user);
    
    // 쿼리 실행
    PrincipalSearchResult<Principal> results = pS.FindAll();
    
    Console.WriteLine("Bad password attempts on {0}:", 
        dt.ToShortDateString());
    foreach (UserPrincipal result in results)
    {
        Console.WriteLine("name: {0}, {1}",
               result.Name,
               result.LastBadPasswordAttempt.Value);
    }
    
    

    사용자 인증
    디렉터리 기반 응용 프로그램을 개발할 때, 특히 AD LDS를 사용하는 경우 디렉터리에 저장된 사용자의 자격 증명을 인증해야 할 때가 많습니다. .NET Framework 3.5가 발표되기 전에 프로그래머들은 System.DirectoryServices의 DirectoryEntry 클래스를 사용하여 내부적으로 강제로 LDAP 바인딩 작업을 수행하여 이를 구현했습니다. 그러나 이러한 코드는 안전성이나 성능이 떨어질 수 있으며 작성하기도 어렵습니다. 또한 ADSI 자체는 이러한 형식의 작업에 맞게 설계되지 않았으며 내부적으로 LDAP 연결을 캐시하는 방식 때문에 사용량이 많은 상황에서는 문제가 발생할 수 있습니다.
    이미 설명했듯이 .NET Framework 2.0의 System.DirectoryServices.Protocols 어셈블리에는 연결 기반 프로그래밍 메타포를 사용하는 저수준 LDAP 클래스가 포함되어 있습니다. 이것은 ADSI 고유의 한계를 극복하기 위한 설계이지만 복잡한 코드를 작성해야 한다는 단점이 있습니다.
    .NET Framework 3.5의 AccountManagement는 프로그래머가 모든 환경에서 작업할 수 있도록 ASP.NET의 ActiveDirectoryMembershipProvider 구현을 통한 강력한 기능과 사용 편의성을 제공합니다. 또한 AccountManagement 네임스페이스는 필요한 경우 로컬 SAM 데이터베이스에 대해 자격 증명을 인증할 수 있도록 합니다.
    PrincipalContext 클래스의 두 ValidateCredentials 메서드를 사용하여 자격 증명 유효성 검사를 수행할 수 있습니다. 먼저 자격 증명의 유효성을 검사할 대상 디렉터리를 사용하여 PrincipalContext의 인스턴스를 만들고 적절한 옵션을 지정합니다. 컨텍스트를 설정한 후 제공한 사용자 이름 및 암호 값에 따라 ValidateCredentials에서 true 또는 false가 반환되는지 테스트합니다. 그림 12는 AD LDS에서 사용자를 인증하는 예를 보여 줍니다.
    // AD LDS를 사용하여 컨텍스트 설정
    PrincipalContext ldsContext = 
        new PrincipalContext(
            ContextType.ApplicationDirectory, 
            "sea-dc-02.fabrikam.com:50000", 
            "ou=ADAM Users,O=Microsoft,C=US");
    
    // 디렉터리에 유효한 사용자인지 확인
    Console.WriteLine(
        ldsContext.ValidateCredentials(
            "user1@adam", 
            "Password1", 
            ContextOptions.SimpleBind + 
            ContextOptions.SecureSocketLayer));
    
    
    이 방법은 다양한 사용자 자격 증명 집합의 유효성을 빠르고 효율적으로 검사할 때 특히 유용합니다. 해당 디렉터리 저장소에 대한 단일 PrincipalContext 개체를 만들어 ValidateCredentials에 대한 각 호출에 반복적으로 사용할 수 있으며 PrincipalContext가 디렉터리에 대한 연결을 재사용할 수 있어 성능과 확장성 면에 유리합니다. 그리고 ValidateCredentials에 대한 호출은 스레드로부터 안전하므로 여러 스레드에 걸쳐 이 작업을 수행하는 데 인스턴스를 사용할 수 있습니다. PrincipalContext를 만드는 데 사용되는 자격 증명은 ValidateCredentials에 대한 호출로 변경되지 않으며 컨텍스트 및 메서드 호출은 별도의 연결을 유지한다는 점이 중요합니다.
    기본적으로 AccountManagement는 보안 Windows 협상 인증을 사용하며 AD LDS에 대해 단순 바인딩을 수행할 때는 SSL 사용을 시도합니다. 따라서 수행할 인증 유형과 사용할 연결 보호 방식(해당하는 경우)을 항상 명시적으로 지정하는 것이 좋지만 기본 설정을 사용하더라도 보안에는 문제가 없습니다.
    Windows Server® 2003 이상 버전의 Active Directory 도메인 서비스 및 AD LDS에는 고성능 인증 작업을 위한 빠른 동시 바인딩 기능이 포함되어 있습니다. 이 기능은 사용자에 대한 보안 토큰을 실제로 만들지 않고 사용자 암호의 유효성을 검사합니다. 일반적인 바인딩 작업과는 달리 빠른 동시 바인딩에서는 LDAP 연결 상태가 바인딩되지 않은 채로 유지됩니다. 동일한 연결에 대해 반복적으로 바인딩 작업을 수행하고 간단히 실패한 암호 시도를 확인하는 데 빠른 동시 바인딩을 사용할 수 있습니다. 이 기능은 ADSI 또는 System.DirectoryServices를 통해 사용할 수 있는 옵션이 아니라 Protocols 네임스페이스에서 제공되는 옵션입니다.
    AccountManagement는 가능한 한 빠른 동시 바인딩을 사용하고 자동으로 이 옵션을 설정합니다. 그림 1의 Protocols 계층 위에 AccountManagement 계층이 있는 것도 이러한 이유에서입니다. 빠른 동시 바인딩은 네트워크에서 일반 텍스트 자격 증명을 전달하는 단순 바인딩 모드에서만 사용할 수 있습니다. 따라서 보안상의 이유로 빠른 동시 바인딩은 항상 SSL과 함께 사용해야 합니다.

    확장성 모델
    AccountManagement가 빛을 발하는 다른 분야로 확장성 모델을 들 수 있습니다. Active Directory 도메인 서비스와 AD LDS 모두에 사용할 수 있는 사용자 지정 프로비전 시스템을 구축하기 위해 다양한 Principal 파생 클래스를 사용하는 개발자가 증가할 것입니다. 조직에서 특히 AD LDS의 경우 사용자와 그룹에 대한 자체 메타데이터를 지원하기 위해 디렉터리에 사용자 지정 스키마 확장을 추가하는 경우도 많아질 것입니다.
    AccountManagement는 .NET Framework 개체 지향 설계 및 특성 기반 확장 가능 메타데이터를 사용함으로써 사용자 지정 스키마를 지원하는 사용자 지정 보안 주체 클래스를 쉽게 만들 수 있게 해 줍니다. Principal 파생 클래스 중 하나에서 상속하고 클래스와 속성을 적절한 특성으로 표시하면 사용자 지정 principal 클래스가 이러한 디렉터리 특성은 물론 기본 제공 형식에서 이미 지원되는 특성을 읽고 쓸 수 있습니다.
    AccountManagement가 제공하는 확장성 메커니즘은 Active Directory 도메인 서비스 또는 AD LDS에 저장된 보안 주체에 사용하도록 설계되었다는 사실을 알아야 합니다. 즉, 비 Microsoft LDAP 디렉터리는 크게 염두에 두지 않은 것입니다. 따라서 비 Microsoft LDAP 디렉터리에서 프로비전에 사용할 프레임워크를 구축하려면 Protocols 네임스페이스의 저수준 클래스를 사용해야 합니다. 또한 SAM 스키마는 확장할 수 없으므로 로컬 SAM 계정에는 확장성 모델이 적합하지 않습니다.
    표준 LDAP 사용자 클래스를 사용하여 응용 프로그램의 보안 주체를 저장하는 AD LDS 디렉터리가 있다고 가정해 보겠습니다. 그리고 msdn-subscriberID라는 사용자 개체를 식별하기 위한 특별한 특성을 지원하도록 LDAP 디렉터리 스키마를 확장했다고 가정합니다. 그림 13에서는 사용자 개체를 프로비전하고 이 특성에 대해 만들기, 읽기 및 쓰기 작업을 제공하는 사용자 지정 클래스를 작성하는 방법을 보여 줍니다.
    [DirectoryObjectClass("user")]
    [DirectoryRdnPrefix("CN")]
    class MsdnUser : UserPrincipal
    {
        public MsdnUser(PrincipalContext context)
            : base(context) { }
    
        public MsdnUser(
            PrincipalContext context,
            string samAccountName,
            string password,
            bool enabled
            )
            : base(
               context,
               samAccountName,
               password,
               enabled
               )
        {
        }
    
        [DirectoryProperty("msdn-subscriberID")]
        public string MsdnSubscriberId
        {
            get
            {
                object[] result = this.ExtensionGet("msdn-subscriberID");
                if (result != null) {
                    return (string)result[0];
                }
                else {
                    return null;
                }
            }
            set { this.ExtensionSet("msdn-subscriberID", value); }
        }
    }
    
    
    이 코드는 UserPrincipal 클래스에서 상속하며 DirectoryObjectClass 및 DirectoryRdnPrefix라는 두 가지 특성이 지정됩니다. 보안 주체 확장 클래스에는 이 두 특성이 모두 필요합니다. DirectoryObjectClass 특성은 지원되는 저장소(Active Directory 도메인 서비스 또는 AD LDS)에서 디렉터리에 이 개체의 인스턴스를 만들 때 objectClass 디렉터리 특성에 사용하는 값을 결정합니다. 여기서는 기본 AD LDS 사용자 클래스이지만 실제로는 다른 어떤 것도 될 수 있습니다. DirectoryRdnPrefix 특성은 디렉터리에서 이 클래스의 개체를 명명하는 데 사용할 RDN(상대 고유 이름) 특성 이름을 결정합니다. Active Directory 도메인 서비스에서는 RDN 접두사를 변경할 수 없으며 보안 주체 클래스의 경우 항상 CN입니다. 그러나 보다 유연한 AD LDS에서는 원하는 경우 다른 RDN을 사용할 수 있습니다.
    예의 클래스에는 문자열을 반환하는 MsdnSubscriberID라는 속성이 있습니다. 이 클래스는 속성 값을 저장하는 데 사용할 LDAP 스키마 특성을 지정하는 DirectoryProperty 특성으로 표시되어 있습니다. 기본 프레임워크는 이 보안 주체 유형에 대한 검색 작업을 최적화하는 데 이 값을 사용합니다.
    예의 속성 get 및 set 구현에서는 Principal 기본 클래스의 protected ExtensionGet 및 ExtensionSet 메서드를 사용하여 기본 속성 캐시에 대해 값을 읽고 씁니다. 이러한 메서드는 아직 데이터베이스/ID 저장소에 저장되지 않은 개체의 값을 메모리 내에 저장하도록 지원하고 기존 개체에서 값을 읽고 쓰는 작업도 지원합니다. LDAP 디렉터리는 다양한 특성 형식을 지원할 뿐만 아니라 특성에 여러 값이 포함될 수 있으므로 이러한 메서드는 object[] 형식을 사용하여 값을 읽고 씁니다. 이러한 유연성은 유용하지만 개체 형식의 배열에 더해 강력한 형식의 스칼라 문자열 값을 제공하려면 예 구현에서 보듯이 추가적인 작업이 필요합니다. 결과적으로 사용자 지정 MsdnUser 클래스의 사용자에게는 프로그래밍이 용이한 인터페이스가 제공됩니다.
    디렉터리 스키마에 더해 강력한 형식의 값까지 제공하는 기능은 이 확장성 모델의 가장 유용한 기능 중 하나입니다. .NET Framework에서 제공하는 다양한 형식 시스템을 사용하면 단순 문자열 형식 외에도 Active Directory 도메인 서비스 jpgPhoto 특성을 일반적으로 System.DirectoryServices에서 값을 읽을 때 얻는 기본 byte[]가 아니라 System.Drawing.Image 또는 System.IO.Stream으로 나타내는 등의 작업을 수행할 수 있습니다.
    이 기사의 코드 다운로드에는 이러한 기능을 보여 주는 몇 가지 다른 예가 있습니다. 또한 MsdnUser 클래스로 테스트 디렉터리를 확장하는 데 사용할 수 있는 스키마 확장도 msdnschema.ldf라는 표준 LDIF 형식 파일로 제공됩니다. "디렉터리 서비스 리소스" 보충 기사의 유용한 링크도 참조하시기 바랍니다.

    결론
    AccountManagement는 Microsoft에서 제공하는 풍부한 디렉터리 서비스 프로그래밍 모델에 절실히 요구되던 관리 코드 추가 기능이라 할 수 있습니다. AccountManagement 네임스페이스가 추가됨에 따라 개발자들은 일반적인 CRUD 및 검색 작업에 강력한 형식의 여러 가지 보안 주체를 사용할 수 있게 되었습니다.
    이 네임스페이스에는 안전하고 성능이 뛰어난 관리 코드를 쉽게 작성할 수 있도록 디렉터리 서비스 프로그래밍과 관련된 최선의 방법이 캡슐화되어 있습니다. 또한 AccountManagement는 확장이 가능하므로 Active Directory 도메인 서비스 및 AD LDS의 사용자 지정 디렉터리 개체와 완벽하게 상호 작용할 수 있습니다.

    Joe Kaplan은 Accenture의 사내 IT 조직에서 .NET Framework를 사용한 엔터프라이즈 응용 프로그램 구축을 담당하고 있습니다. 또한 응용 프로그램 보안, 페더레이션 ID 관리 및 디렉터리 서비스 프로그래밍 전문가로서 이 분야에서 Microsoft MVP로 선정되기도 하였으며 .NET Developer's Guide to Directory Services Programming(Addison-Wesley, 2006)의 공동 저자이기도 합니다.

    Ethan Wilansky Microsoft Directory Services MVP이자 EDS 엔터프라이즈 설계자입니다. Ethan은 EDS Innovation Engineering의 일환으로, 현재 사용자 지정 SharePoint 응용 프로그램 개발에 집중하고 있는 개발 팀을 이끌며 EDS에 디렉터리 서비스 프로그래밍 솔루션과 관련한 자문을 제공하고 있습니다.
    Posted by tornado
    |
    원본 : http://angeleyes.tistory.com/73
    지현~ 땡큐

    윈도우 서비스 실행 시 에러가 나게 되면.
    에러 메시지를 볼 수 있는 방법은 로그를 찍던지 해야 됩니다만..


    그런 불편함을 항상 가지고 갈 수 없습니다..

    코드를 아래와 같이 작성 하게 되면…

    1. protected override void OnStart(string[] args)  
    2. {  
    3.     // TODO: 여기에 서비스를 시작하는 코드를 추가합니다.  
    4.     System.Diagnostics.Debugger.Launch();  
    5.     FileWatcher fw = new FileWatcher(System.Windows.Forms.Application.StartupPath);  
    6.     fw.OnInit();  
    7. }  




    위의 이미지를 띄워서 디버깅 걸 수 있습니다.

    윈도우 서비스가 실행 될때가 아니라면 Process Attach 로 확인 할 수 있습니다만.
    위 방법은 서비스 실행 시 방법입니다.
    Posted by tornado
    |
    출처 : http://support.microsoft.com/?kbid=313114


    Create a new C# program

    1. In Visual C# 2005 or in Visual C# .NET, create a new C# console program that is named MBTest.
    2. In Solution Explorer, right-click References, and then click Add Reference.
    3. On the .NET tab, add a project reference to the System.DirectoryServices namespace.
    4. On the COM tab, add a reference to Microsoft CDO for Exchange Management.
    5. Replace the code in the Class1.cs file with the following code.

      Note In Visual C# 2005, replace the code in the Program.cs file instead.
      using System;
      using CDOEXM;
      using System.DirectoryServices;
      
      namespace MBTest
      {
           class Class1
           {
                [STAThread]
                static void Main(string[] args)
                {
                     //TODO: Change these items to values for your domain or organization.
                     string defaultNC = "DC=yourdomain,DC=com";
                     string alias = "jsmith"; 
                     string fullName = "Joseph Smith";
                     string password = "TestMb123.";
                     string domainName = "yourdomain.com";
                     string homeMDB = "CN=Mailbox Store (Your Server),CN=Your Storage Group," 
                               + "CN=InformationStore,CN=Your Server,CN=Servers,"
                               + "CN=Your Administrative Group,CN=Administrative Groups,"
                               + "CN=Your Org,CN=Microsoft Exchange,CN=Services,"
                               + "CN=Configuration,DC=Yourdomain,DC=Com";
      
                     DirectoryEntry container, user;
                     CDOEXM.IMailboxStore mailbox;
      
                     //This creates the new user in the "users" container.
                     //Set the sAMAccountName and the password
                     container = new DirectoryEntry("LDAP://cn=users," + defaultNC);
                     user = container.Children.Add("cn=" + fullName, "user");
                     user.Properties["sAMAccountName"].Add(alias);
                     user.CommitChanges();
                     user.Invoke("SetPassword", new object[]{password});
                     
                     //This enables the new user.
                     user.Properties["userAccountControl"].Value = 0x200; //ADS_UF_NORMAL_ACCOUNT
                     user.CommitChanges();
      
                     //Obtain the IMailboxStore interface, create the mailbox, and commit the changes.
                     mailbox = (IMailboxStore)user.NativeObject;
                     mailbox.CreateMailbox(homeMDB);
                     user.CommitChanges();
      
                     return;
                }
           }
      }
      					
    6. Change the variables in the TODO section in the Main function so that they contain the correct values for your domain.
    7. Compile the project, and then run the program.
    8. Confirm that the new account was created in the domain by starting the Active Directory Users and Computers snap-in in Microsoft Management Console (MMC). You see the new user in the Users container. To verify that this user is mailbox-enabled, view the user's properties and note that the Exchange tabs appear, and that a mailbox store is listed for the user on the Exchange General tab.

    Code description

    Create a new DirectoryEntry

    This code demonstrates how to bind to a container (in this case, the Users container), and how to create a new user in the container. Do not forget the "cn=" entry for the new user's name.
    container = new DirectoryEntry("LDAP://cn=users," + defaultNC);
    user = container.Children.Add("cn=" + fullName, "user");
    				

    Set properties on the new user

    1. Assign a value for sAMAccountName. This is a mandatory attribute. The user account is not created if you do not specify a value.
    2. Because you have supplied the mandatory attributes, call CommitChanges to save the new user in the directory.
    3. Call IADs::SetPassword to set the password. You must do this after a call to CommitChanges.
    4. Enable the user by modifying the userAccountControl attribute.
      user.Properties["sAMAccountName"].Add(alias);
      user.CommitChanges();
      user.Invoke("SetPassword", new object[]{password});
                     
      //This enables the new user:
      user.Properties["userAccountControl"].Value = 0x200; //ADS_UF_NORMAL_ACCOUNT
      user.CommitChanges();
      				

    Create a new mailbox

    1. To get the IMailboxStore interface, cast DirectoryEntry.NativeObject to this type. This cast does not succeed at run time if CDOEXM is not installed on a computer.
    2. Call the CreateMailbox method, and pass a valid distinguished name to a mailbox store in your Exchange organization.
    3. Call CommitChanges on DirectoryEntry to save the new mailbox.
      //Obtain the IMailboxStore interface, create the mailbox, and commit the changes.
      mailbox = (IMailboxStore)user.NativeObject;
      mailbox.CreateMailbox(homeMDB);
      user.CommitChanges();
      				

    Troubleshooting

    • You must have appropriate permissions in the domain to create a user and a mailbox. Typically, to create mailbox-enabled users in a Windows 2000-based domain, you must be a member of the Windows 2000 Domain Administrators group for the domain.
    • If this code runs on a computer other than an Exchange 2000 Server-based computer, you must have Exchange 2000 System Management Tools installed on the computer. If you do not, CDOEXM is not available and the cast to the IMailboxStore interface throws an InvalidCastException response:
      An unhandled exception of type 'System.InvalidCastException' occurred in MBTest.exe
      Additional information: Specified cast is not valid.
    • If you receive an error message on the call to IMailboxStore.CreateMailbox, verify that the parameter that you passed to this method is a valid mailbox store in your organization. If it is not, you receive an error message that is similar to the following:
      An unhandled exception of type 'System.Runtime.InteropServices.COMException' occurred in MBTest.exe
      Additional information: There is no such object on the server.
    Posted by tornado
    |

    http://adriandev.blogspot.com/2008/01/listening-to-calendar-events-with.html

    Friday, January 11, 2008
    Listening to Calendar Events with Outlook 2007 and VSTO
    Keywords: .NET, Outlook 2007, Visual Studio 2008, VSTO
    I've recently being trying to listen to events from a user's calendars so that I can track when appointments are added, removed and modified, and ran into a couple of problems.

    Problem 1: Events intermittently stop firing

    I was listening to events on the MAPIFolder object and the Calendar's Items collection and they would work for a while and at some point would stop firing.

    I eventually traced this down to what I believe is a COM reference counting issue. I wasn't storing a reference to these objects, so I guess there was no implicit AddRef by the interop library. As such, there was no need for the runtime to keep the object around.

    Hanging on to these references appears to be resolving this issue, so I stand by my assumptions.

    Problem 2: How do we know what has been deleted?

    The events I was hooking up to were on the ItemsEvents_Event interface off the MAPIFolder.Items property. There are thee events on this interface:


    ItemAdd

    ItemChange

    ItemRemove


    The first two have a single parameter which will be the AppointmentItem that is either being added or changed. Great! The last one doesn't have any parameters - all it tells you is that an item has been removed, but doesn't give you any indication what has been deleted. Besides that, its actually too late anyway, the item has already been deleted (you cannot cancel it).

    So, I am now hooking up to the BeforeItemMove event on the MAPIFolderEvents_12_Event interface off the MAPIFolder object. This gives me a reference to the AppointmentItem being moved, the folder in which it is being moved to, and also allows me to cancel it.

    To detect that it is being deleted, just check the folder it is being moved to is null (hard-delete such as Shift-Del) or the deleted items folder (soft-delete).

    Solution

    I have created a class that will monitor a user's default calendar and fires three events:

    AppointmentAdded - gives you the AppointmentItem instance added.

    AppointmentModified - gives you the AppointmentItem instance changed.

    AppointmentDeleting - gives you the AppointmentItem that is about to be deleted and allows you to cancel the operation if needed.


    Sample usage:

    The following code is for use within the ThisAddIn class. It outputs to console when an item is added, modified, or about to be deleted. It also displays a dialog box confirming an item should be deleted.
    private void ThisAddIn_Startup(object sender, System.EventArgs e)
    {
      CalendarMonitor monitor = new CalendarMonitor(this.Application.Session);
      monitor.AppointmentAdded +=
          new EventHandler<EventArgs<AppointmentItem>>(monitor_AppointmentAdded);
      monitor.AppointmentModified +=
          new EventHandler<EventArgs<AppointmentItem>>(monitor_AppointmentModified);
      monitor.AppointmentDeleting +=
          new EventHandler<CancelEventArgs<AppointmentItem>>(monitor_AppointmentDeleting);
    }

    private void monitor_AppointmentAdded(object sender, EventArgs<AppointmentItem> e)
    {
      Debug.Print("Appointment Added: {0}", e.Value.GlobalAppointmentID);
    }

    private void monitor_AppointmentModified(object sender, EventArgs<AppointmentItem> e)
    {
      Debug.Print("Appointment Modified: {0}", e.Value.GlobalAppointmentID);
    }

    private void monitor_AppointmentDeleting(object sender, CancelEventArgs<AppointmentItem> e)
    {
      Debug.Print("Appointment Deleting: {0}", e.Value.GlobalAppointmentID);
      DialogResult dr = MessageBox.Show("Delete appointment?", "Confirm",
      MessageBoxButtons.YesNo, MessageBoxIcon.Question);
      if (dr == DialogResult.No)
      {
          e.Cancel = true;
      }
    }

    There are two other classes used: EventArgs and CancelEventArgs. These generics are used by the events to pass strongly typed appointments.

    EventArgs.cs:
    using System;
    using System.Collections.Generic;

    namespace OutlookAddIn2
    {
      public class EventArgs<T> : EventArgs
      {
          private T _value;

          public EventArgs(T aValue)
          {
              _value = aValue;
          }

          public T Value
          {
              get { return _value; }
              set { _value = value; }
          }
      }
    }

    CancelEventArgs.cs:
    using System;
    using System.Collections.Generic;

    namespace OutlookAddIn2
    {
      public class CancelEventArgs<T> : EventArgs<T>
      {
          private bool _cancel;

          public CancelEventArgs(T aValue)
              : base(aValue)
          {
          }

          public bool Cancel
          {
              get { return _cancel; }
              set { _cancel = value; }
          }
      }
    }

    CalendarMonitor.cs:
    using System;
    using System.Collections.Generic;
    using Microsoft.Office.Interop.Outlook;

    namespace OutlookAddIn2
    {
      public class CalendarMonitor
      {
          private NameSpace _session;
          private List<string> _folderPaths;
          private List<MAPIFolder> _calendarFolders;
          private List<Items> _calendarItems;
          private MAPIFolder _deletedItemsFolder;

          public event EventHandler<EventArgs<AppointmentItem>> AppointmentAdded;
          public event EventHandler<EventArgs<AppointmentItem>> AppointmentModified;
          public event EventHandler<CancelEventArgs<AppointmentItem>> AppointmentDeleting;

          public CalendarMonitor(NameSpace aSession)
          {
              _folderPaths = new List<string>();
              _calendarFolders = new List<MAPIFolder>();
              _calendarItems = new List<Items>();

              _session = aSession;
              _deletedItemsFolder = aSession.GetDefaultFolder(
                  OlDefaultFolders.olFolderDeletedItems);

              HookupDefaultCalendarEvents();
          }

          private void HookupDefaultCalendarEvents()
          {
              MAPIFolder folder = _session.GetDefaultFolder(
                  OlDefaultFolders.olFolderCalendar);
              if (folder != null)
              {
                  HookupCalendarEvents(folder);
              }
          }

          private void HookupCalendarEvents(MAPIFolder aCalendarFolder)
          {
              if (aCalendarFolder.DefaultItemType != OlItemType.olAppointmentItem)
              {
                  throw new ArgumentException("The MAPIFolder must use " +
                      "AppointmentItems as the default type.");
              }

              if (_folderPaths.Contains(aCalendarFolder.FolderPath) == false)
              {
                  Items items = aCalendarFolder.Items;
                  //
                  // Store folder path to prevent double ups on our listeners.
                  //
                  _folderPaths.Add(aCalendarFolder.FolderPath);
                  //
                  // Store a reference to the folder and to the items collection
                  // so that it remains alive for as long as we want. This keeps
                  // the ref count up on the underlying COM object and prevents
                  // it from being intermittently released (then the events don't
                  // get fired).
                  //
                  _calendarFolders.Add(aCalendarFolder);
                  _calendarItems.Add(items);
                  //
                  // Add listeners for the events we need.
                  //
                  ((MAPIFolderEvents_12_Event)aCalendarFolder).BeforeItemMove +=
                      new MAPIFolderEvents_12_BeforeItemMoveEventHandler(Calendar_BeforeItemMove);
                  items.ItemChange +=
                      new ItemsEvents_ItemChangeEventHandler(CalendarItems_ItemChange);
                  items.ItemAdd +=
                      new ItemsEvents_ItemAddEventHandler(CalendarItems_ItemAdd);
              }
          }

          private void CalendarItems_ItemAdd(object anItem)
          {
              if (anItem is AppointmentItem)
              {
                  if (this.AppointmentAdded != null)
                  {
                      this.AppointmentAdded(this,
                          new EventArgs<AppointmentItem>((AppointmentItem)anItem));
                  }
              }
          }

          private void CalendarItems_ItemChange(object anItem)
          {
              if (anItem is AppointmentItem)
              {
                  if (this.AppointmentModified != null)
                  {
                      this.AppointmentModified(this,
                          new EventArgs<AppointmentItem>((AppointmentItem)anItem));
                  }
              }
          }

          private void Calendar_BeforeItemMove(object anItem, MAPIFolder aMoveToFolder,
              ref bool Cancel)
          {
              if ((aMoveToFolder == null) || (IsDeletedItemsFolder(aMoveToFolder)))
              {
                  if (anItem is AppointmentItem)
                  {
                      if (this.AppointmentDeleting != null)
                      {
                          //
                          // Listeners to the AppointmentDeleting event can cancel
                          // the move operation if moving to the deleted items folder.
                          //
                          CancelEventArgs<AppointmentItem> args =
                              new CancelEventArgs<AppointmentItem>((AppointmentItem)anItem);
                          this.AppointmentDeleting(this, args);
                          Cancel = args.Cancel;
                      }
                  }
              }
          }

          private bool IsDeletedItemsFolder(MAPIFolder aFolder)
          {
              return (aFolder.EntryID == _deletedItemsFolder.EntryID);
          }
      }
    }
     

    20 comments:

    Anonymous said...
    Thanks so much for this! I would have never figured it out.

    23 April 2008 04:12
    Adrian Brown said...
    I've been looking up other people's posts for help for years. I'm glad I've helped at least one person!

    23 April 2008 09:02
    Anonymous said...
    Hi Adrian, thank you so much for posting this code. I have finally managed to get Stephen Toub code "Custom Calendar Providers for Outlook 2003" to work but I have one doubt, which I need your help for. I need to be able delete an event from Outlook calendar when an event from my webservice is deleted. Would I be able to use your code to accomplish this???

    24 April 2008 17:52
    Anonymous said...
    Hi Adrian, I was reading through your code "CalendarMonitor" class, you have a variable called "MAPIFolderEvents_12_Event", can you tell me what this is as it's not been declared anywhere in your code????

    24 April 2008 22:33
    Adrian Brown said...
    The code here only listens to events of a Calendar in Outlook (it won't delete, add, move anything itself), specifically 2007. I believe there are some small differences between 2007 and 2003, but you should still be able to use this code as a basis.

    As for the MAPIFolderEvents_12_Event reference, this is an interface implemented by the MAPIFolder object. From memory, I think you have to explicitly cast the MAPIFolder object to MAPIFolderEvents_12_Event to get access to the events. You should find this defined in the Outlook COM interop library that is referenced as part of the Visual Studio Outlook AddIn Project.

    25 April 2008 12:27
    Anonymous said...
    Hi Adrian, I'm glad to see I'm not the only one that had this issue. I was able to accomplish removing an appointment from the db when the user deleted an appointment from the calendar by returning all the items in the Deleted Items folder and then looping through those items to return the AppointmentItem for each item in the folder by using the following code.


    Outlook.MAPIFolder deletedItemsFolder =
    Application.Session.GetDefaultFolder(OlDefaultFolders.olFolderDeletedItems);

    Outlook.Items deletedAppointments = deletedItemsFolder.Items;

    foreach (AppointmentItem outlookAppointment in deletedAppointments)
    {
    if (outlookAppointment != null)
    {
    object objAppointmentID = GetUserProperty(outlookAppointment, APPOINTMENT_ID_KEY);
    if (objAppointmentID == null) { return; }
    int appointmentID = int.Parse(objAppointmentID.ToString());

    Appointment appointment = Appointment.GetAppointment(appointmentID);

    appointment.Delete();

    If you see any issues with doing it this way please respond so I can fix them.

    Thanks

    20 June 2008 22:19
    Adrian Brown said...
    The only issue I can see with this method is you are assuming that all deleted items go to the deleted items folder.

    Appointments can be deleted directly (Shift-Del is the shortcut I think) and this won't get picked up by that code.

    Cheers,
    Adrian.

    26 June 2008 09:12
    Anonymous said...
    Hi Adrian,
    this post is really helped me a lot. This is great code i was looking for. i have small issue in my outlook add-in, when user changes the time of the appointment item through the calender dragging, it fires item change event. I want to cancel Item Change event in certain conditions. Is it possible to cancel Item Change change event.

    Thanks & Regards,
    Sandeep

    26 August 2008 11:57
    Adrian Brown said...
    Sandeep,

    Good question, but I don't know. From what I have seen, it looks unlikely.

    Cheers,
    Adrian.

    27 August 2008 16:27
    Sandeep said...
    hi Adrian,
    Your code works great if i make any change in appointment or click on delete Appointment. If i send meeting invitation and and then cancel meeting it doesn't capture this event. I think BeforeItemMove should capture this event, but it doesn't. Can you please tell me how to capture cancel meeting event?

    Thanks & Regards,
    Sandeep

    27 August 2008 22:14
    WAQ said...
    Hi Adrian,

    A very useful post.

    Just a question for you see if you can help. How we can trap the item before delete event in outlook 2000 or if there is any other work around we can use by implementing some other events?

    Thanks in advance

    10 September 2008 00:42
    Adrian Brown said...
    I'm sorry WAQ, I don't know with Outlook 2000.

    10 September 2008 09:04
    Alex said...
    This post has been removed by the author.
    12 September 2008 23:46
    Alex said...
    Hi Adrian, loving the code you've posted although for some reason the MAPIFolderEvents_12_Event.BeforeItemMove event doesn't seem to fire on a custom calendar. Any ideas why this might be?

    Thanks in advance,

    Alex

    13 September 2008 00:37
    EagleSigma said...
    Great Post Adrian! Thanks to you I was able to actually get my application running the way I wanted.

    Just one thing is bugging me: If I create or edit an appointment using Outlook 2007 everthing works as expected and I'm able to retrieve the "e.Value.Organizer" value. However, if a user does the same using Outlook web access 2003 - Premium or Basic, that value comes back with the vale "Error."

    Any guidedance or suggestions woould be greatly appreciated. Thanks again.

    14 September 2008 05:43
    Adrian Brown said...
    Hi Alex,

    The code posted won't work on a custom calendar because it is only listening to events on the default calendar.

    You'll have to loop through all of the MAPIFolders looking for anything that is a calendar.

    Cheers,
    Adrian.

    15 September 2008 17:21
    Adrian Brown said...
    Hi eaglesigma,

    Yeah, I haven't really played around much with OWA. It wasn't a requirement for me, so I deliberately chose to ignore it. :o)

    Sorry, and good luck!
    Adrian.

    15 September 2008 17:23
    Alex said...
    Hi Adrian, I've actually modified the code you posted to take in an array of MAPIFolders when constructing the CalendarMonitor object so it it is listening to events on the my calendar. The item events fire but not the folder event as mentioned before...?

    15 September 2008 17:45
    Adrian Brown said...
    Alex,

    Hmmm... can't see why the folder event does not fire. I don't think I've tried it with a custom calendar myself and haven't really looked at this Outlook stuff for quite a while now.

    I'm really sorry but I don't have the time to look into this right away.

    If you have any luck getting it to work or find out why it doesn't, please let me know. Otherwise I'll try and get to it sometime over the next week or so (which is probably way too late for you).

    Cheers,
    Adrian.

    15 September 2008 17:58
    William said...
    Hello Adrian,

    The modify event seems to be firing too early.

    Here is the issue:

    Sometimes when the calendar has multiple updates the entry shows up
    on the calendar view but when I
    access the properties, such as Start or Subject, it does not have the new values- it has the previous values that were present before the modify event fired.

    The tricky part is that on the calendar view, the tooltip balloon shows the correct and latest data-but if you double click the item to open it, it still has the the old data. Somehow, the tooltip control is able to access the latest data before the item actually receives the changes.

    The problem is that my code checks for the Start time and Subject to decide how to process the item; so when I get the Modify event and my code launches, it doesn't do anything because the two fields are still the same.

    With 5 to 10 minutes eventually all the items in the calendar receive the latest data. However, at that point no event is triggered to let me process the modified entries.

    What can I use in my Modify event that will force the public folder to give me the latest properties for the item?
    Thanks you for your help.

    William

    9 October 2008 03:21

    Posted by tornado
    |

    아주 예~~전에 만들었던 소스인데, 컴퓨터 정리중 나옴.

    마이크로소프트 개발자 사이트에서 본 내용에 살좀 더 붙이고 내가 쓰기좋게 개량한 것.

    -------------------------------------------------------------------------------


    using System;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Reflection;
    using System.Collections.Specialized;

    using System.Text;
    using System.Data;
    using System.Collections;

    namespace xxx.xxx.xxx
    {
        /// <summary>
        /// Summary description for PropertyBinder
        /// </summary>
        public class PropertyBinder : LoggingBase
        {

            public static void copyAllField(object src, object dest)
            {
                try
                {
                    if (dest == null)
                    {
                        throw new Exception("dest is null..");
                    }

                    if (src == null)
                    {
                        throw new Exception("src is null..");
                    }

                    Type t = src.GetType();
                    Type destType = dest.GetType();

                    FieldInfo[] f = t.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
                    FieldInfo[] destFields = destType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);

                    for (int i = 0; i < f.Length; i++)
                    {
                        destFields[i].SetValue(dest, f[i].GetValue(src));
                    }
                }
                catch (Exception e)
                {
                    throw e;
                }
            }


            /// <summary>
            /// 컨트롤에 입력된 값을 지정된 Object 에 대입한다.
            /// CheckBox 는 bool 형태로 입력되며,
            /// RadioButton 은 bool 형태와 Y / N 의 형태로 입력되며,
            /// RadioButtonList 는 해당 List 의 값으로 입력되며,
            /// CheckBoxList 는 선택된 CheckBox 의 Value 값들이 val1, val2, val3 의 형태로 입력되며,
            /// DropDownList(Select 중 Single 형태)는 선택된 값이 입력되며,
            /// DropDownList(Select 중 Multiple 형태)는 선택된 값들이 val1, val2, val3 의 형태로 입력된다.
            /// </summary>
            /// <param name="obj">Property Object</param>
            /// <param name="container">웹 폼의 Page 객체</param>
            public static void BindControlsToObject(object obj, Page page)
            {
                if (obj == null) return;

                Type objType = obj.GetType();
                PropertyInfo[] objPropertiesArray = objType.GetProperties();

                MasterPage master = page.Master;

                foreach (PropertyInfo objProperty in objPropertiesArray)
                {
                    Control control;

                    if (master != null)
                    {
                        ContentPlaceHolder holder = (ContentPlaceHolder)page.Master.FindControl("ContentPlaceHolder1");
                        control = holder.FindControl(objProperty.Name);
                    }
                    else
                    {
                        control = page.FindControl(objProperty.Name);
                    }
                    if (control is ListControl)
                    {
                        ListControl listControl = (ListControl)control;

                        if (listControl.Items.Count > 1)
                        {
                            if (listControl is CheckBoxList)
                            {
                                CheckBoxList chkList = (CheckBoxList)listControl;

                                string val = "";

                                for (int i = 0; i < chkList.Items.Count; i++)
                                {
                                    if (chkList.Items[i].Selected)
                                    {
                                        val += chkList.Items[i].Value + ",";
                                    }
                                }

                                if ((val.Length - 1) == val.LastIndexOf(",") && val.Length > 0)
                                {
                                    val = val.Substring(0, val.Length - 1);
                                }

                                objProperty.SetValue(obj, val, null);
                            }
                            else if (listControl is ListBox)
                            {
                                ListBox box = (ListBox)control;

                                string val = "";

                                for (int i = 0; i < box.Items.Count; i++)
                                {
                                    if (box.Items[i].Selected)
                                    {
                                        val += box.Items[i].Value + ",";
                                    }
                                }

                                if ((val.Length - 1) == val.LastIndexOf(",") && val.Length > 0)
                                {
                                    val = val.Substring(0, val.Length - 1);
                                }

                                objProperty.SetValue(obj, val, null);
                            }
                            else if (listControl.SelectedItem != null)
                            {
                                objProperty.SetValue(obj, Convert.ChangeType(listControl.SelectedItem.Value, objProperty.PropertyType), null);
                            }
                        }
                    }
                    else if (control is CheckBox)
                    {
                        CheckBox ch = (CheckBox)control;

                        if (objProperty.PropertyType == typeof(bool))
                        {
                            objProperty.SetValue(obj, Convert.ChangeType(ch.Checked, objProperty.PropertyType), null);
                        }
                        else if (objProperty.PropertyType == typeof(string))
                        {
                            if (ch.Checked)
                            {
                                objProperty.SetValue(obj, Convert.ChangeType("Y", objProperty.PropertyType), null);
                            }
                            else
                            {
                                objProperty.SetValue(obj, Convert.ChangeType("N", objProperty.PropertyType), null);
                            }
                        }
                    }
                    else if (control is RadioButton)
                    {
                        RadioButton radio = (RadioButton)control;

                        if (objProperty.PropertyType == typeof(bool))
                        {
                            objProperty.SetValue(obj, Convert.ChangeType(radio.Checked, objProperty.PropertyType), null);
                        }
                        else if (objProperty.PropertyType == typeof(string))
                        {
                            objProperty.SetValue(obj, Convert.ChangeType(radio.Text, objProperty.PropertyType), null);
                        }
                    }
                    else if (control is TextBox)
                    {
                        TextBox tx = (TextBox)control;

                        objProperty.SetValue(obj, Convert.ChangeType(tx.Text, objProperty.PropertyType), null);
                    }
                    else if (control is Label)
                    {
                        Label lbl = (Label)control;
                        objProperty.SetValue(obj, Convert.ChangeType(lbl.Text, objProperty.PropertyType), null);
                    }
                    else if (control is Calendar)
                    {
                        Calendar c = (Calendar)control;
                        if (objProperty.PropertyType == typeof(DateTime))
                        {
                            objProperty.SetValue(obj, Convert.ChangeType(c.SelectedDate, objProperty.PropertyType), null);
                        }
                        else if (objProperty.PropertyType == typeof(string))
                        {
                            objProperty.SetValue(obj, Convert.ChangeType(c.SelectedDate.ToString(), objProperty.PropertyType), null);
                        }
                    }
                }
            }


            #region Property 객체에 있는 값을 컨트롤에 대입한다.
            /// <summary>
            /// Property 객체에 있는 값을 컨트롤에 대입한다.
            /// </summary>
            /// <param name="obj">각 프라퍼티에 값들이 채워져 있는 Object</param>
            /// <param name="page">컨트롤이 있는 Page 객체</param>
            public static void BindObjectToControls(object obj, Page page)
            {
                if (obj == null) return;
                Type objType = obj.GetType();
                PropertyInfo[] objPropertiesArray = objType.GetProperties();

                MasterPage master = page.Master;

                Control control = null;

                foreach (PropertyInfo objProperty in objPropertiesArray)
                {

                    if (master != null)
                    {
                        ContentPlaceHolder holder = (ContentPlaceHolder)page.Master.FindControl("ContentPlaceHolder1");

                        if (holder != null)
                        {
                            control = holder.FindControl(objProperty.Name);
                        }
                    }
                    else
                    {
                        control = page.FindControl(objProperty.Name);
                    }

                   
                    if (control != null)
                    {
                        if (control is ListControl)
                        {

                            if (objProperty.GetValue(obj, null) != null)
                            {
                                ListControl listControl = (ListControl)control;
                               
                                if (listControl.Items.Count > 0)
                                {
                                    if (listControl is CheckBoxList)
                                    {
                                        CheckBoxList ch = (CheckBoxList)listControl;

                                        string[] val = objProperty.GetValue(obj, null).ToString().Split(',');

                                        for (int i = 0; i < ch.Items.Count; i++)
                                        {
                                            for (int j = 0; j < val.Length; j++)
                                            {
                                                if (ch.Items[i].Value.ToLower().Equals(val[j].ToString().ToLower()))
                                                {
                                                    ch.Items[i].Selected = true;
                                                }
                                            }
                                        }
                                    }
                                    else if (listControl is ListBox)
                                    {

                                        ListBox box = (ListBox)listControl;

                                        string[] val = objProperty.GetValue(obj, null).ToString().Split(',');

                                        for (int i = 0; i < box.Items.Count; i++)
                                        {
                                            for (int j = 0; j < val.Length; j++)
                                            {
                                                if (box.Items[i].Value.ToLower().Equals(val[j].ToString().ToLower()))
                                                {
                                                    box.Items[i].Selected = true;
                                                }
                                            }
                                        }
                                    }
                                    else if (listControl is RadioButtonList)
                                    {
                                        RadioButtonList rbl = (RadioButtonList)listControl;

                                        if (objProperty.GetValue(obj, null) != null)
                                        {
                                            foreach (ListItem item in rbl.Items)
                                            {
                                                if (objProperty.GetValue(obj, null).ToString().ToLower().Equals(item.Value.ToLower()))
                                                {
                                                    item.Selected = true;
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                    else if (listControl is DropDownList)
                                    {
                                        DropDownList ddl = (DropDownList)listControl;

                                        if (objProperty.GetValue(obj, null) != null)
                                        {
                                            foreach (ListItem item in ddl.Items)
                                            {
                                                if (objProperty.GetValue(obj, null).ToString().ToLower().Equals(item.Value.ToLower()))
                                                {
                                                    item.Selected = true;
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                }
                                else
                                {
                                    string propertyValue = objProperty.GetValue(obj, null).ToString();
                                    ListItem listItem = listControl.Items.FindByValue(propertyValue);
                                    if (listItem != null) listItem.Selected = true;
                                }
                            }
                        }
                        else if (control is CheckBox)
                        {
                            if (objProperty.PropertyType == typeof(bool))
                            {
                                ((CheckBox)control).Checked = (bool)objProperty.GetValue(obj, null);
                            }
                            else if (objProperty.PropertyType == typeof(string))
                            {
                                if (objProperty.GetValue(obj, null) != null && objProperty.GetValue(obj, null).ToString().ToLower().Equals("y"))
                                {
                                    ((CheckBox)control).Checked = true;
                                }
                            }
                        }
                        else if (control is TextBox)
                        {
                            if (objProperty.GetValue(obj, null) != null)
                                ((TextBox)control).Text = "" + objProperty.GetValue(obj, null).ToString();
                        }
                        else if (control is Label)
                        {
                            if (objProperty.GetValue(obj, null) != null)
                                ((Label)control).Text = "" + objProperty.GetValue(obj, null).ToString();
                        }
                        else if (control is Calendar)
                        {
                            if (objProperty.GetValue(obj, null) != null)
                            {
                                if (objProperty.PropertyType == typeof(DateTime))
                                {
                                    ((Calendar)control).SelectedDate = (DateTime)objProperty.GetValue(obj, null);
                                }
                            }
                        }
                    }
                }
            }
            #endregion

            #region DataTable ==> Control 로 바인딩 합니다.
            public static void BindDataTableToControls(DataTable dt, Page page)
            {
                MasterPage master = page.Master;

                Control control = null;

                ContentPlaceHolder holder = (ContentPlaceHolder)master.FindControl("ContentPlaceHolder1");


                for (int ii = 0; ii < dt.Rows.Count; ii++)
                {
                    for (int jj = 0; jj < dt.Columns.Count; jj++)
                    {
                        if (master != null)
                        {
                            if (holder != null)
                            {
                                control = holder.FindControl(dt.Columns[jj].ColumnName);
                            }
                        }
                        else
                        {
                            control = page.FindControl(dt.Columns[jj].ColumnName);
                        }

                        if (control != null)
                        {
                            if (control is ListControl)
                            {
                                if (dt.Rows[ii][jj] != null)
                                {
                                    ListControl listControl = (ListControl)control;

                                    if (listControl.Items.Count > 0)
                                    {
                                        if (listControl is CheckBoxList)
                                        {
                                            CheckBoxList ch = (CheckBoxList)listControl;

                                            string[] val = dt.Rows[ii][jj].ToString().Split(',');

                                            for (int i = 0; i < ch.Items.Count; i++)
                                            {
                                                for (int j = 0; j < val.Length; j++)
                                                {
                                                    if (ch.Items[i].Value.ToLower().Equals(val[j].ToString().ToLower()))
                                                    {
                                                        ch.Items[i].Selected = true;
                                                    }
                                                }
                                            }
                                            continue;
                                        }
                                        else if (listControl is ListBox)
                                        {

                                            ListBox box = (ListBox)listControl;

                                            string[] val = dt.Rows[ii][jj].ToString().Split(',');

                                            for (int i = 0; i < box.Items.Count; i++)
                                            {
                                                for (int j = 0; j < val.Length; j++)
                                                {
                                                    if (box.Items[i].Value.ToLower().Equals(val[j].ToString().ToLower()))
                                                    {
                                                        box.Items[i].Selected = true;
                                                    }
                                                }
                                            }
                                            continue;
                                        }
                                        else if (listControl is RadioButtonList)
                                        {
                                            RadioButtonList rbl = (RadioButtonList)listControl;

                                            if (dt.Rows[ii][jj] != null)
                                            {
                                                foreach (ListItem listItem in rbl.Items)
                                                {
                                                    if (dt.Rows[ii][jj].ToString().ToLower().Equals(listItem.Value.ToLower()))
                                                    {
                                                        listItem.Selected = true;
                                                        break;
                                                    }
                                                }
                                            }
                                            continue;
                                        }
                                        else if (listControl is DropDownList)
                                        {
                                            DropDownList ddl = (DropDownList)listControl;

                                            if (dt.Rows[ii][jj] != null)
                                            {
                                                foreach (ListItem listItem in ddl.Items)
                                                {
                                                    if (dt.Rows[ii][jj].ToString().ToLower().Equals(listItem.Value.ToLower()))
                                                    {
                                                        listItem.Selected = true;
                                                        break;
                                                    }
                                                }
                                            }
                                            continue;
                                        }
                                    }
                                    else
                                    {
                                        string propertyValue = dt.Rows[ii][jj].ToString();
                                        ListItem listItem = listControl.Items.FindByValue(propertyValue);
                                        if (listItem != null) listItem.Selected = true;
                                    }
                                }
                            }
                            else if (control is CheckBox)
                            {
                                if (dt.Columns[jj].DataType == typeof(bool))
                                {
                                    ((CheckBox)control).Checked = (bool)dt.Rows[ii][jj];
                                }
                                else if (dt.Columns[jj].DataType == typeof(string))
                                {
                                    if (dt.Rows[ii][jj] != null && dt.Rows[ii][jj].ToString().ToLower().Equals("y"))
                                    {
                                        ((CheckBox)control).Checked = true;
                                    }
                                }

                                continue;
                            }
                            else if (control is TextBox)
                            {
                                if (dt.Rows[ii][jj] != null)
                                    ((TextBox)control).Text = "" + dt.Rows[ii][jj].ToString();
                                continue;
                            }
                            else if (control is Label)
                            {
                                if (dt.Rows[ii][jj] != null)
                                    ((Label)control).Text = "" + dt.Rows[ii][jj].ToString();
                                continue;
                            }
                            else if (control is Calendar)
                            {
                                if (dt.Rows[ii][jj] != null)
                                {

                                    if (dt.Columns[jj].DataType == typeof(DateTime))
                                    {
                                        ((Calendar)control).SelectedDate = (DateTime)dt.Rows[ii][jj];
                                    }
                                }
                                continue;
                            }
                        }
                    }
                }
            }
            #endregion


            #region Request.Params 컬렉션을 Object 로 매핑합니다
            public static void CopyProperty(NameValueCollection src, object dest)
            {
                if (src == null) throw new Exception("src is null..");
                if (dest == null) throw new Exception("dest obj is null..");

                Type t = dest.GetType();

                FieldInfo[] f = t.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);

                string key = "";

                for (int i = 0; i < src.Count; i++)
                {
                    key = src.GetKey(i);

                    for (int j = 0; j < f.Length; j++)
                    {
         try
         {
          if (key.Equals(f[j].Name))
          {
           if ("System.Int32".Equals(f[j].FieldType.ToString()))
           {
            f[j].SetValue(dest, Int32.Parse(src.Get(key)));
           }
           else if ("System.Int64".Equals(f[j].FieldType.ToString()))
           {
            f[j].SetValue(dest, Int64.Parse(src.Get(key)));
           }
           else if ("System.Single".Equals(f[j].FieldType.ToString()))
           {
            f[j].SetValue(dest, Single.Parse(src.Get(key)));
           }
           else if ("System.Double".Equals(f[j].FieldType.ToString()))
           {
            f[j].SetValue(dest, Double.Parse(src.Get(key)));
           }
           else if ("System.Boolean".Equals(f[j].FieldType.ToString()))
           {
            f[j].SetValue(dest, Boolean.Parse(src.Get(key)));
           }
           else if ("System.Decimal".Equals(f[j].FieldType.ToString()))
           {
            f[j].SetValue(dest, Decimal.Parse(src.Get(key)));
           }
           else if ("System.String".Equals(f[j].FieldType.ToString()))
           {
            f[j].SetValue(dest, src.Get(key));
           }
           else if ("System.Object".Equals(f[j].FieldType.ToString()))
           {
            f[j].SetValue(dest, src.Get(key));
           }
           else if ("System.DateTime".Equals(f[j].FieldType.ToString()))
           {
            f[j].SetValue(dest, src.Get(key));
           }
          }
         }
         catch
         {      
         }
                    }
                }
            } // end
            #endregion


            public static void MasterPageCopyProperty(NameValueCollection src, object dest)
            {
                if (src == null) throw new Exception("src is null..");
                if (dest == null) throw new Exception("dest obj is null..");

                string tmp = "";

                Type t = dest.GetType();

                FieldInfo[] f = t.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);

                string key = "";

                for (int i = 0; i < src.Count; i++)
                {
                    key = src.GetKey(i);

                    if (key.LastIndexOf("$") > -1)
                    {
                        tmp = key.Substring(key.LastIndexOf("$") + 1);

                        for (int j = 0; j < f.Length; j++)
                        {
                            if (tmp.Equals(f[j].Name))
                            {
                                if ("System.Int32".Equals(f[j].FieldType.ToString()))
                                {
                                    if (CheckNullToInt(src.Get(key).ToString()))
                                    {
                                        f[j].SetValue(dest, Convert.ToInt32(src.Get(key)));
                                    }
                                }
                                else if ("System.Int64".Equals(f[j].FieldType.ToString()))
                                {
                                    f[j].SetValue(dest, Convert.ToInt64(src.Get(key)));
                                }
                                else if ("System.Single".Equals(f[j].FieldType.ToString()))
                                {
                                    f[j].SetValue(dest, Single.Parse(src.Get(key)));
                                }
                                else if ("System.Double".Equals(f[j].FieldType.ToString()))
                                {
                                    f[j].SetValue(dest, Double.Parse(src.Get(key)));
                                }
                                else if ("System.Boolean".Equals(f[j].FieldType.ToString()))
                                {
                                    f[j].SetValue(dest, Boolean.Parse(src.Get(key)));
                                }
                                else if ("System.Decimal".Equals(f[j].FieldType.ToString()))
                                {
                                    f[j].SetValue(dest, Decimal.Parse(src.Get(key)));
                                }
                                else if ("System.String".Equals(f[j].FieldType.ToString()))
                                {
                                    f[j].SetValue(dest, src.Get(key));
                                }
                                else if ("System.Object".Equals(f[j].FieldType.ToString()))
                                {
                                    f[j].SetValue(dest, src.Get(key));
                                }
                                else if ("System.DateTime".Equals(f[j].FieldType.ToString()))
                                {
                                    f[j].SetValue(dest, src.Get(key));
                                }
                            }
                        }
                    }
                }
            } // end


            private static bool CheckNullToInt(string args)
            {
                if (args != null && args.Length > 0)
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }


      public static string HtmlToString(object obj)
      {
       if(obj == null)  return "obj 가 널이야!! 조사하면 다나와~~!!";
       
       Type t = obj.GetType();

       FieldInfo[] f = t.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);

       StringBuilder builder = new StringBuilder("<Font color=\"\">");

       for(int i = 0; i < f.Length; i++)
       {    

        if(f[i].GetValue(obj) != null)
        {
         builder.Append(f[i].Name.ToString() + " : " + f[i].GetValue(obj) + "<BR>");
        }
        else
        {
         builder.Append(f[i].Name.ToString() + " : null<BR>");
        }
       }

       builder.Append("</FONT>");
       

       return builder.ToString();


      }


            public static string AlertString(object obj)
            {
                if (obj == null) return "obj 가 널이야!! 조사하면 다나와~~!!";

                Type t = obj.GetType();

                FieldInfo[] f = t.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);

                StringBuilder builder = new StringBuilder("");

                for (int i = 0; i < f.Length; i++)
                {

                    if (f[i].GetValue(obj) != null)
                    {
                        builder.Append(f[i].Name.ToString() + " : " + f[i].GetValue(obj) + "\r\n");
                    }
                    else
                    {
                        builder.Append(f[i].Name.ToString() + " : null\r\n");
                    }
                }

                builder.Append("");


                return builder.ToString();


            }


            public static DataSet ConvertObjectToDataSet(ArrayList arr)
            {
                DataSet ds = new DataSet();

                DataTable dt = null;

                if (arr == null || arr.Count == 0)
                {
                    return ds;
                }

                Object obj = arr[0];

                if (obj != null)
                {
                    Type t = obj.GetType();

                    dt = new DataTable(t.Name);
                    FieldInfo[] f = t.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);


                    // 데이터 컬럼을 먼저 채운다.
                    for (int i = 0; i < f.Length; i++)
                    {
                        if (f[i].Name != null)
                        {
                            dt.Columns.Add(f[i].Name.ToString(), f[i].FieldType);
                        }
                    }

                    foreach (object tmp in arr)
                    {
                        t = tmp.GetType();

                        f = t.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);

                        DataRow tmpRow = dt.NewRow();

                        for (int i = 0; i < f.Length; i++)
                        {
                            if ("System.Int32".Equals(f[i].FieldType.ToString()))
                            {
                                if (CheckNullToInt(f[i].GetValue(obj).ToString()))
                                {
                                    tmpRow[dt.Columns[i].ToString()] = PublicFunction.Int(f[i].GetValue(obj).ToString());
                                }
                            }
                            else if ("System.Int64".Equals(f[i].FieldType.ToString()))
                            {
                                tmpRow[dt.Columns[i].ToString()] = PublicFunction.Int64(f[i].GetValue(obj).ToString());
                            }
                            else if ("System.Single".Equals(f[i].FieldType.ToString()))
                            {
                                tmpRow[dt.Columns[i].ToString()] = Single.Parse(f[i].GetValue(obj).ToString());
                            }
                            else if ("System.Double".Equals(f[i].FieldType.ToString()))
                            {
                                tmpRow[dt.Columns[i].ToString()] = Double.Parse(f[i].GetValue(obj).ToString());
                            }
                            else if ("System.Boolean".Equals(f[i].FieldType.ToString()))
                            {
                                tmpRow[dt.Columns[i].ToString()] = Boolean.Parse(f[i].GetValue(obj).ToString());
                            }
                            else if ("System.Decimal".Equals(f[i].FieldType.ToString()))
                            {
                                tmpRow[dt.Columns[i].ToString()] = Decimal.Parse(f[i].GetValue(obj).ToString());
                            }
                            else if ("System.String".Equals(f[i].FieldType.ToString()))
                            {
                                tmpRow[dt.Columns[i].ToString()] = f[i].GetValue(obj).ToString();
                            }
                            else if ("System.Object".Equals(f[i].FieldType.ToString()))
                            {
                                tmpRow[dt.Columns[i].ToString()] = f[i].GetValue(obj);
                            }
                            else if ("System.DateTime".Equals(f[i].FieldType.ToString()))
                            {
                                tmpRow[dt.Columns[i].ToString()] = Convert.ToDateTime(f[i].GetValue(obj));
                            } 
                           
                        }
                           
                        dt.Rows.Add(tmpRow);

                    }

                    ds.Tables.Add(dt);

                }


                return ds;
            }
           

        }


    }

    Posted by tornado
    |

    펌 : http://blog.naver.com/basis_ryuki?Redirect=Log&logNo=30026875105

    기순씨... 쌩유~



    1.vsto제대로 깔고 보자.

    - vsto runtime은 기본적으로 오피스2007 -> vs2005 설치순으로 했을때 Vs2005설치시에 깔려주신다..


    - vsto runtime깔렸으니까 괜찮겠지 믿고나서 뒤에 큰코 다치지 말고.. 하나더 깔아야 한다.

       (안그럼 솔루션 파일 열었을때  "C:\Program Files\MSBuild\Microsoft.VisualStudio.OfficeTools2.targets"가 없다는 둥 에러가 뜬다..)

      : Visual Stuio Tool for Office sp2


      그렇게 깔아서 정상으로 나왔을때 C:\Program Files\MSBuild\는 다음과 같다.


    - 여기서 만사 오케 하시면 감사하겠지만.. 좀 수틀려 버리시면.. 솔루션을 열고 나서 Outlook reference참조 에러도 난다.. (망할녀석!!)

       office를 나중에 설치했을때 문제라고 하는데 정상 설치했을때도 가끔 그러는것 같다..


      정확히는 "Microsoft.Office.Interop"에러라고 빌드시에 나온다.. 별별짓을 다해도 소용없다.

      우왕 좌왕하다가 포맷말고..(난 벌써 했단말야..ㅠㅠ) 아래 링크에 가서 파일 다운 ..

     

      http://www.microsoft.com/downloads/details.aspx?familyid=59DAEBAA-BED4-4282-A28C-B864D8BFA513&displaylang=en

    - 설치하고 솔루션을 다시 열었을대 outlook reference참조가 정상으로 되어있음 확인!

       빌드 Succeeded!!


    =========================================================================================

    [펌]컴퓨터에 설치된 VSTO 2005 Runtime의 버전을 알아보려면..

    -출처 : 김유철 책임 블로그에서..^^;;


  • 레지스트리 설정: 다음 레지스트리 설정을 확인하십시오.
    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\vsto runtime Setup\v8.0.50727
    • “Update = 2”가 나타나는 경우 VSTO Runtime의 8.0.50727.891 버전이 이미 설치되어 있으므로 이 다운로드를 설치하지 않아도 됩니다.
    • “Update = 1”이 나타나는 경우 원래 VSTO 2005 SE Runtime이 설치되어 있습니다(버전 8.0.50727.816). 이 다운로드를 설치하는 것이 좋습니다.
    • 레지스트리 키가 나타나지만 Update 설정이 없는 경우 first-edition VSTO 2005 Runtime이 설치되어 있습니다(버전 8.0.50727.42). 이 다운로드를 설치하는 것이 좋습니다.
    • 위의 키를 찾을 수 없는 경우 설치된 VSTO 2005 Runtime이 없습니다. 이 다운로드를 설치하는 것이 좋습니다.
  • 파일 버전: Program Files\Common Files\Microsoft Shared\VSTO\8.0\에 있는 AddinLoader.dll이라는 파일을 검사하여 파일 버전 번호를 확인할 수 있습니다.
    • 이 런타임 버전의 파일 버전 번호는 8.0.50727.891입니다.
    • 원래 VSTO 2005 SE 파일 버전 번호는 8.0.50727.816입니다.
    • first-edition VSTO 2005 파일 버전 번호는 8.0.50727.42입니다

      [출처] 험난한 vsto 설치..|작성자 유키

  • Posted by tornado
    |

    출처 : http://blogs.msdn.com/vikas/archive/2007/07/21/getting-into-a-folder-by-path-using-ews.aspx



    Getting into a folder by path using EWS

    Exchange Web Service is very powerful but everything has some limitations.

    I've been struggling with one limitation that you cannot access any folder in Mailbox using its path like

    /Inbox/MyFolder1/MyAnotherFolder/ThisIsTheFolderIWant

    Client is not aware of the folder hierarchy on server side so you cannot access any folder directly, you need to create your own mechanism for that.

    I am sharing the sample code I have created to Traverse down to the Folder path one by one and finally return the last folder.

    Approach

    -          Suppose you want to get into “/Inbox/First/Second/Final” folder

    -          Get root folder’s ID

    -          Find Inbox using root folder’s ID

    -          Find First using Inbox folder’s ID

    -          and so on till Final folder

    -          Remember you should call FindFolder with FullString ContainmentModeType to get the desired result, otherwise you might get wrong folder

    Code

    Note: I have tested this code on Exchange 2007 RTM with Visual Studio 2005 , and it should work fine

        class Program

        {

            static void Main(string[] args)

     

            {

     

                ExchangeServiceBinding esb = new ExchangeServiceBinding();

                esb.Credentials = new System.Net.NetworkCredential("UserName", "Password");

                esb.Url = "http://YourExchangeServerHere/ews/exchange.asmx";

     

                BaseFolderType bft = GetFolderByPath(esb, "/Inbox/Folder1/Folder2/Folder3/FinalFolder");

               

                if (null == bft)

                    Console.WriteLine("Folder not found.");

                else

                    Console.WriteLine("Folder Found: " + bft.DisplayName);

                return;

                

            }

     

            static public BaseFolderType GetFolderByPath(ExchangeServiceBinding esb, string szFolderPath)

            {

                if (null == szFolderPath)

                    return null;

     

                if (szFolderPath.IndexOf("/") == -1)

                    return null;

     

                if (szFolderPath.Substring(0, 1).CompareTo("/") == 0)

                    szFolderPath = szFolderPath.Substring(1);

               

                string[] Path=szFolderPath.Split('/');

                string szParentFolderId = null;

                BaseFolderType bft = null;

     

                for (int i = 0; i < Path.GetLength(0); i++)

                {

                    if (null == szParentFolderId)

                        szParentFolderId = GetRootFolderId(esb);

     

                    bft = GetFolder(esb, szParentFolderId, Path[i]);

                    if (null == bft)

                        return null;

                    else

                        szParentFolderId = bft.FolderId.Id;

                }

                return bft;

            }

            static public BaseFolderType GetFolder(ExchangeServiceBinding esb, string szParentFolderId, string szFolderName)

            {

                if (null == esb || null == szFolderName)

                    return null;

                //get the root folder ID

                FolderIdType[] fit = new FolderIdType[1];

                fit[0] = new FolderIdType();

                fit[0].Id = szParentFolderId;

                //set the props that we want to retrieve

                FolderResponseShapeType frst = new FolderResponseShapeType();

                frst.BaseShape = DefaultShapeNamesType.AllProperties;

                //restrict the search on the folder name

                PathToUnindexedFieldType ftFolderName = new

                PathToUnindexedFieldType();

                ftFolderName.FieldURI = UnindexedFieldURIType.folderDisplayName;

                ConstantValueType cvt = new ConstantValueType();

                cvt.Value = szFolderName;

                FieldURIOrConstantType ctFolderName = new FieldURIOrConstantType();

                ctFolderName.Item = cvt;

                ContainsExpressionType cet = new ContainsExpressionType();

                cet.Constant = cvt;

                cet.Item = ftFolderName;

     

                cet.ContainmentComparison = ContainmentComparisonType.IgnoreCase;

                cet.ContainmentComparisonSpecified = true;

                cet.ContainmentMode = ContainmentModeType.FullString;

                cet.ContainmentModeSpecified = true;

                RestrictionType rt = new RestrictionType();

                rt.Item = cet;

                //find the folder

                FindFolderType fft = new FindFolderType();

                fft.Traversal = FolderQueryTraversalType.Deep;

                fft.ParentFolderIds = fit;

                fft.FolderShape = frst;

                fft.Restriction = rt;

                FindFolderResponseType ffrt = esb.FindFolder(fft);

                ResponseMessageType rmt =

                ((ResponseMessageType)ffrt.ResponseMessages.Items[0]);

                if (rmt.ResponseClass == ResponseClassType.Success)

                {

                    BaseFolderType[] bfts = ((FindFolderResponseMessageType)ffrt.ResponseMessages.Items[0]).RootFolder.Folders;

                    if (bfts.GetLength(0) > 0)

                        return bfts[0];

                    else

                        return null;

                }

     

                else

                    return null;

            }

            static public string GetRootFolderId(ExchangeServiceBinding esb)

            {

                if (null == esb)

                    return null;

     

                DistinguishedFolderIdType[] dfit = new DistinguishedFolderIdType[1];

               

                    //get the root folder ID

                    dfit[0] = new DistinguishedFolderIdType();

                    dfit[0].Id = DistinguishedFolderIdNameType.root;

               

                //set the props that we want to retrieve

                FolderResponseShapeType frst = new FolderResponseShapeType();

                frst.BaseShape = DefaultShapeNamesType.AllProperties;

                //get the folder

                GetFolderType gftRoot = new GetFolderType();

                gftRoot.FolderIds = dfit;

               

                gftRoot.FolderShape = frst;

                GetFolderResponseType gfrt = esb.GetFolder(gftRoot);

                FolderInfoResponseMessageType firmt =

                ((FolderInfoResponseMessageType)gfrt.ResponseMessages.Items[0]);

                if (firmt.ResponseClass == ResponseClassType.Success)

                    return ((FolderInfoResponseMessageType)gfrt.ResponseMessages.Items[0]).Folders[0].FolderId.Id;

                else

                    return null;

            }

        }

     



    Posted by tornado
    |

    Configuring Context-Sensitive Custom Tabs MOC2007

    How to configure custom content sensitive tabs in Microsoft Office Communicator MOC2007?

    Well its very simple to do. For more indepth information see Communicator_2007_Deployment_Guide from pagr 14.

    Setting up a custom tab requires:

    • A network-accessible custom tab definition file in XML format that gives the location of each tab’s main page and controls the information Communicator passes to this page.
    • A registry entry in the \Software\Policies\Microsoft\Communicator registry hive that gives the location of the tab definition file.

    · A graphic in Portable Network Graphics (PNG) format for the tab. The graphic can be:

    • Up to 16 pixels high by 16 pixels wide, or
    • Exactly 32 pixels high by 32 pixels wide.

    One or more Web pages for display in the Office Communicator window.

    Step 1. Create a custom XML file to prepare your MOC2007 configuration:

    Example:

    <?xml version="1.0" ?> 
    <tabdata>
    <tab>
       <image>/.png">http://<COMPUTERNAME>/<IMAGE>.png</image>
       <name>Smoelenboek</name>
       <tooltip>Tooltip text</tooltip> 
       <contenturl> /">http://<WEBURL>/</contenturl>  
       <userid>true</userid>
       <contactid>true</contactid>
       <accessibility>inside</accessibility>
    </tab>
    </tabdata>

    Step 2. Customize the registry in:

    HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Communicator

    The TabURL setting can be added to either section of the registry. However, settings in the hkey_local_machine\software section take precedence.

    image

    The HTML file containing the tab content must be in a trusted location on the network. The XML and PNG files can be stored in the same location as the HTML file, or on a network server or local machine. For example:

    · file:///\\contonso-it\oc\tabs.xml

    · file:///c:/myTab/tabs.xml

    Example:

    file:///c:/temp/customtab.xml

    Step 3. Restart your MOC2007 client and there you go ;-)

    image

    Posted by tornado
    |
    [펌] SQL Server Explorer 만들기;
    [원글] http://blog.vuscode.com/malovicn/archive/2007/11/12/how-to-build-your-own-sql-server-explorer.aspx



    .NET exploration, articles, cool links, surf logs, book reviews, .net, c#, smart clients, software factories, patterns & practices, web casts and much more

    How to build your own SQL Server Explorer

    Recently, I've started making in my free time my own little ORM tool, mainly as an fun way into exploration of the ADO .NET 2.0

    Every  dissent ORM tool has to be based on DB object enumeration activities and I've noticed there are not a lot straight "how to" articles on net how to do various things (at least I had a hard time finding them), so I decided to make a simple blog post describing exactly those How-To's

    How to enumerate visible SQL server instances

    Enumerating visible instances of MS SQL Server can be performed by executing the GetDataSource() of the SqlDataSourceEnumerator type singleton instance.

    GetDataSource returns four column data table with next columns:

    1. ServerName - Name of the server.
    2. InstanceName - Name of the server instance. Blank if the server is running as the default instance.
    3. IsClustered - Indicates whether the server is part of a cluster.
    4. Version - Version of the server (8.00.x for SQL Server 2000, and 9.00.x for SQL Server 2005).

    Code example:

       1: public static IList<string> GetActiveServers()
       2: {
       3:     Collection<string> result = new Collection<string>();
       4:     SqlDataSourceEnumerator instanceEnumerator = SqlDataSourceEnumerator.Instance;
       5:     DataTable instancesTable = instanceEnumerator.GetDataSources();
       6:     foreach (DataRow row in instancesTable.Rows)
       7:     {
       8:         if (!string.IsNullOrEmpty(row["InstanceName"].ToString()))
       9:             result.Add(string.Format(@"{0}\{1}", row["ServerName"], row["InstanceName"]));
      10:         else
      11:             result.Add(row["ServerName"].ToString());
      12:     }
      13:     return result;
      14: }

    How to enumerate databases of the given SQL server instance

    Enumerating the databases of the given server can be performed by executing the GetSchema method of the SqlConnection instance with a SqlClientMetaDataCollectionNames.Databases string enumeration value passed to method.

    Passing that enumeration or it's string equivalent ("Databases") is totally the same, except enumeration looks cooler :)

    Code example:

       1: public static IList<string> GetDatabases(string serverName, string userId, string password,
       2:                                               bool windowsAuthentication)
       3:      {
       4:          Collection<string> result = new Collection<string>();
       5:          using (
       6:              SqlConnection connection =
       7:                  GetActiveConnection(serverName, string.Empty, userId, password, windowsAuthentication))
       8:          {
       9:              connection.Open();
      10:              DataTable dt = connection.GetSchema(SqlClientMetaDataCollectionNames.Databases);
      11:              foreach (DataRow row in dt.Rows)
      12:              {
      13:                  result.Add(string.Format("{0}", row[0]));
      14:              }
      15:          }
      16:          return result;
      17:      }

    In line 6, we are using an instance of SqlConnection type created by GetActiveConnection method.

    In line 10, we are calling GetSchema connection instance method which returns a data table with a single column which contains the name of the database

    in ADO NET, methods used for retrieving schema information always are overloaded with a version accepting additional string array parameter which is used for passing the restrictions (you can think of it as a filter criteria) which ADO.NET should apply while retrieving resulting set.
    Retrieving database schema in our example has only one restriction and that is database name so if we would write something like

       DataTable dt = connection.GetSchema("Databases", new string[] {"Northwind" });

    Please notice in that that line that I used "Databases" and not enumeration and that I have passed single string array with "Nortwind" content.
    Result of passing that restriction would be that ADO NET would retrieve only databases fulfilling the restriction requirement, which means only Nortwind database data would be returned

    GetActiveConnection method creates a new SqlConnection instance using SqlConnectionStringBuilder class which is used to build connection string for given parameters.

    Something like this

       1: private static SqlConnection GetActiveConnection(string serverName, string databaseName, string userName,
       2:                                                  string password, bool useIntegratedSecurity)
       3: {
       4:     SqlConnectionStringBuilder connBuilder = new SqlConnectionStringBuilder();
       5:     connBuilder.DataSource = serverName;
       6:     connBuilder.InitialCatalog = databaseName;
       7:     connBuilder.IntegratedSecurity = useIntegratedSecurity;
       8:     connBuilder.UserID = userName;
       9:     connBuilder.Password = password;
      10:     return new SqlConnection(connBuilder.ConnectionString);
      11: }

    I'll be using this helper methods also in rest of the examples

    How to enumerate tables of the given database

    In general, the procedure of retrieval tables is the same as the procedure described for databases, in a sense that the GetSchema method of SqlConnection instance is been called but this time with SqlClientMetaDataCollectionNames.Tables ("Tables") enumerated value.

    The big difference between those two is in the fact that tables restriction are contained of four different constraint arguments:

    1. database name
    2. owner/schema name ("dbo")
    3. table name (which should contain null value if we want to retrieve all tables of database)
    4. table type (which can have values "VIEW" for views and "BASE TABLE" for tables

    So, to retrieve the list of tables for a given database we could use code similar to the next one:

       1: public static IList<string> GetTables(string serverName, string databaseName, string userId, string password,
       2:                                       bool windowsAuthentication)
       3: {
       4:     string[] restrictions = new string[4];
       5:     restrictions[0] = databaseName; // database/catalog name   
       6:     restrictions[1] = "dbo"; // owner/schema name   
       7:     restrictions[2] = null; // table name   
       8:     restrictions[3] = "BASE TABLE"; // table type    
       9:     Collection<string> result = new Collection<string>();
      10:     using (
      11:         SqlConnection connection =
      12:             GetActiveConnection(serverName, databaseName, userId, password, windowsAuthentication))
      13:     {
      14:         connection.Open();
      15:         DataTable dt = connection.GetSchema(SqlClientMetaDataCollectionNames.Tables, restrictions);
      16:         foreach (DataRow row in dt.Rows)
      17:         {
      18:             if (!row[2].ToString().StartsWith("sys"))
      19:                 result.Add(string.Format(@"{0}", row[2]));
      20:         }
      21:     }
      22:     return result;
      23: }
    Column keys of the columns of the data table returned are:
    • Column 0. "table_catalog"
    • Column 1. "table_schema"
    • Column 2. "table_name"
    • Column 3. "table_type"

    How to enumerate columns of the given table

    The list of restriction parameters for column retrieval is shorter and it contains next 3 string values:

    1. Database name
    2. Owner/schema name
    3. Table name

    SqlConnection instance GetSchema method gets this time SqlClientMetaDataCollectionNames.Columns ("Columns") enumerated value and the resulting data table contains next 18 columns:

    Column 0 - "TABLE_CATALOG"
    Column 1 - "TABLE_SCHEMA"
    Column 2 - "TABLE_NAME"
    Column 3 - "COLUMN_NAME"
    Column 4 - "ORDINAL_POSTION"
    Column 5 - "COLUMN_DEFAULT"
    Column 6 - "IS_NULLABLE"
    Column 7 - "DATA_TYPE"
    Column 8 - "CHARACTER_MAXIMUM_LENGTH"
    Column 9 - "CHARACTER_OCTET_LENGTH"
    Column 10 - "NUMERIC_PRECISION"
    Column 11 - "NUMERIC_PRECISION_RADIX"
    Column 12 - "NUMERIC_SCALE"
    Column 13 - "DATETIME_PRECISION"
    Column 14 - "CHARACTER_SET_CATALOG"
    Column 15 - "CHARACTER_SET_SCHEMA"
    Column 16 - "CHARACTER_SET_NAME"
    Column 17 - "COLLATION_CATALOG"

    I believe column names are self explanatory and familiar to all of us, so I'll skip explanation of what they stand for

    In our little example we would return concatenated string containing the column name and data type, where data type in case of char data types would show maximal number  of characters and in case of decimal precision data.

    The code doing that might look like this:

       1: public static IList<string> GetColumns(
       2:        string serverName, string databaseName, string userId,
       3:        string password, bool windowsAuthentication, string tableName)
       4:    {
       5:        SqlConnection connection =
       6:            GetActiveConnection(serverName, databaseName, userId, 
       7:                                password, windowsAuthentication);
       8:  
       9:        string[] restrictions = new string[3];
      10:        restrictions[0] = connection.Database; // database/catalog name      
      11:        restrictions[1] = "dbo"; // owner/schema name      
      12:        restrictions[2] = tableName; // table name      
      13:        IList<string> result = new Collection<string>();
      14:        using (connection)
      15:        {
      16:            connection.Open();
      17:            DataTable columns = connection.GetSchema(SqlClientMetaDataCollectionNames.Columns, restrictions);
      18:            foreach (DataRow row in columns.Rows)
      19:            {
      20:                string columnName = row[3].ToString();
      21:                string columnDataType = row[7].ToString();
      22:                if (columnDataType.IndexOf("char") > -1)
      23:                {
      24:                    // row[8] - CHARACTER_MAXIMUM_LENGTH    
      25:                    columnDataType = string.Format("{0}({1})", columnDataType, row[8]);
      26:                }
      27:                if (columnDataType.IndexOf("decimal") > -1)
      28:                {
      29:                    // row[10] - CHARACTER_OCTET_LENGTH    
      30:                    // row[11] - NUMERIC_PRECISION    
      31:                    columnDataType = string.Format("{0}({1},{2})", columnDataType, row[10], row[11]);
      32:                }
      33:                result.Add(string.Format("{0},{1}", columnName, columnDataType));
      34:            }
      35:            return result;
      36:        }
      37:    }

    How to enumerate indexes of the table

    List of restrictions which can be used for indexes is the same as the one used for table, with 4 elements: database name, schema, table name and table type

    We are executing GetSchema method of SqlConnection instance with SqlClientMetaDataCollectionNames.IndexColumns ("IndexColumns") enumerated value sent as a parameter and the resulting data table contains next 9 columns

    • Column 0 - "constraint_catalog"
    • Column 1 - "constraint_schema"
    • Column 2 - "constraint_name"
    • Column 3 - "table_catalog"
    • Column 4 - "table_schema"
    • Column 5 - "table_name"
    • Column 6 - "column_name"
    • Column 7 - "ordinal_position"
    • Column 8 - "KeyType"
    • Column 8 - "index_name"

    Column 8 ("KeyType") describes the data type of the index and contains a numeric value which points to certain data type.

    There's a list:

    34 :  image
    35 :  text
    48 :  tinyint
    52 :  smallint
    56 :  int
    58 :  smalldatetime
    59 :  real
    60 :  money
    61 :  datetime
    62 :  float
    98 :  sql_variant
    99 :  ntext
    104 :  bit
    106 :  decimal
    108 :  numeric
    122 :  smallmoney
    127 : bigint
    165 :  varbinary
    167 :  varchar
    173 :  binary
    175 :  char
    189 :  timestamp
    231 :  nvarchar
    239 :  nchar

    So to enumerate indexes, one might write next code:

       1: public static IList<string> GetIndexes(SqlConnection connection, string tableName)
       2:       {
       3:           string[] restrictions = new string[3];
       4:           restrictions[0] = connection.Database; // database/catalog name      
       5:           restrictions[1] = "dbo"; // owner/schema name      
       6:           restrictions[2] = tableName; // table name      
       7:           IList<string> result = new Collection<string>();
       8:           using (connection)
       9:           {
      10:               connection.Open();
      11:               DataTable columns = connection.GetSchema(SqlClientMetaDataCollectionNames.IndexColumns, restrictions);
      12:               foreach (DataRow row in columns.Rows)
      13:               {
      14:                   string columnName = row["column_name"].ToString();
      15:                   string indexName = row["index_name"].ToString();
      16:                   bool isPrimaryKey = row["constarint_name"].ToString().StartsWith("PK");
      17:                   result.Add(string.Format("Index:{0}, on column:{1}, PK:{2}", indexName, columnName, isPrimaryKey));
      18:               }
      19:               return result;
      20:           }
      21:       }

    How to enumerate parameters of the stored procedure

    Enumeration of parameters used in a stored procedure is been done through usage of the SqlCommandBuilder static DeriveParameters method which accepts the SqlCommand instance constructed for a given sql connection and ctored procedure

    According to http://www.codeproject.com/useritems/DetermineSql2005SPParams.asp, there is a difference in how SQL 2000 and SQL 2005 and there's a need of handling that problem with some additional approach, but according to my personal experience that's not the case - I never had problems he described.
    So, IMHO to enumerate parameters of a stored procedure next simple code should be used regardless of the SQL version:

       1: public static SqlParameter[] DiscoverStoredProcedureParameters(SqlConnection sqlConnection,
       2:                                                                string storedProcedureName)
       3: {
       4:     SqlCommand cmd = new SqlCommand(storedProcedureName, sqlConnection);
       5:     cmd.CommandType = CommandType.StoredProcedure;
       6:     using (sqlConnection)
       7:     {
       8:         sqlConnection.Open();
       9:         SqlCommandBuilder.DeriveParameters(cmd);
      10:     }
      11:     SqlParameter[] discoveredParameters = new SqlParameter[cmd.Parameters.Count];
      12:     cmd.Parameters.CopyTo(discoveredParameters, 0);
      13:     return discoveredParameters;
      14: }

    Test drive

    Bellow you can find a source code of a small example which enumerates the databases (on left), tables of selected database (top right) and columns selected table (right down).

    image[5]


    Conclusion

    ADO NET 2.0 removes the need of using ADOX or SQLDMO components for the tasks covering examining the structure of the database objects. It is almost trivial (with a bit reading of documentation) to do the thing which were before not-so trivial. But...
    Although already very simple to use, I would like to see in future ADO NET 3.0 version  next enhancements:

    • Replacing the property bags as a way of passing arguments with DTOs as data carriers instead of string arrays. I guess that would have to be done in some new SQL connection related helper class to preserve compatibilty
    • For the same reasons I don't like property bags as a method parameter data carriers, I don't like the result data tables
      Having LINQ in place, I don't see the reason why we won't replace the returning DataTables with some more OOP friendly solution

    You can download source code of this example here




    Posted by tornado
    |

    음... 프로젝트 하나 생성하고..
    프로젝트에 System.Management 를 참조시킨다.

    그리고...

    using System;
    using System.Management; 

    로 사용할 네임스페이스 지정하고...

    메서드를 하나 만든담에

    아래와 같이 코딩한다..


            ManagementObject disk = new ManagementObject("win32_logicaldisk.deviceid=\"c:\"");

            disk.Get();

            Console.WriteLine("Logical Disk Size = " + disk["Size"] + " bytes");  
            Console.WriteLine("Logical Disk FreeSpace = " + disk["FreeSpace"] + " bytes"); 

    ------------------------------------------------------------

    참고 : http://msdn2.microsoft.com/en-us/library/aa394173.aspx




    Posted by tornado
    |
    출처 : http://msdn2.microsoft.com/ko-kr/library/3517w44b(VS.80).aspx


    .NET Framework 개발자 가이드 
    SQL Server에 BLOB 값을 쓸 때 리소스 절약 

    데이터베이스의 필드 형식에 따라 필드에 문자열 값이나 바이트 배열을 삽입하거나 업데이트하여 데이터베이스에 BLOB(Binary Large Object)를 쓸 수 있습니다(데이터 소스에 BLOB 값 쓰기 참조). 그러나 BLOB가 너무 크면 단일 값으로 쓸 때 시스템 메모리를 너무 많이 차지하여 응용 프로그램 성능이 낮아질 수 있습니다.

    BLOB 값을 쓸 때 사용되는 메모리 양을 줄이는 일반적인 방법은 데이터베이스에 BLOB를 "청크"로 쓰는 것입니다. 이 방식으로 데이터베이스에 BLOB를 쓰는 과정은 데이터베이스의 성능에 따라 달라집니다.

    다음 샘플에서는 SQL Server에 BLOB를 청크로 쓰는 방법을 보여 줍니다. 이 샘플에서는 Northwind 데이터베이스의 Employees 테이블에 BLOB인 직원 이미지를 비롯한 새 레코드를 추가합니다. 즉, SQL Server의 UPDATETEXT 함수를 사용하여 Photo 필드에 새로 추가한 직원의 이미지를 지정된 크기의 청크 단위로 씁니다.

    UPDATETEXT 함수를 사용하려면 업데이트되는 BLOB 필드에 대한 포인터가 있어야 합니다. 이 샘플에서는 새 직원 레코드를 추가할 때 SQL Server TEXTPTR 함수가 호출되어 새 레코드의 Photo 필드에 대한 포인터가 반환됩니다. 반환된 포인터 값은 출력 매개 변수로 다시 전달됩니다. 이 샘플 코드에서는 이 포인터가 유지되며 데이터의 청크를 추가할 때 UPDATETEXT로 전달됩니다.

    다음은 새 직원 레코드를 삽입하고 Photo 필드에 대한 포인터를 유지하는 데 사용되는 Transact-SQL의 예제입니다. 여기서 @Identity@PointerSqlCommand의 출력 매개 변수로 인식됩니다.

    INSERT INTO Employees (LastName, FirstName, Title, HireDate, ReportsTo, Photo) 
      Values(@LastName, @FirstName, @Title, @HireDate, @ReportsTo, 0x0)
    SELECT @Identity = SCOPE_IDENTITY()
    SELECT @Pointer = TEXTPTR(Photo) FROM Employees WHERE EmployeeID = @Identity

    0x0의 초기 값(null)이 Photo 필드에 삽입되기 때문에 새로 삽입하는 레코드의 Photo 필드에 대한 포인터 값을 검색할 수 있습니다. 그러나 null 값은 추가된 데이터 청크에는 영향을 주지 않습니다.

    새로 삽입한 레코드에 Photo 필드에 대한 포인터가 유지되면 이 샘플을 통해 SQL Server의 UPDATETEXT 함수를 사용하여 BLOB 필드에 데이터 청크를 추가할 수 있습니다. UPDATETEXT 함수는 Employees.Photo와 같은 필드 식별자, BLOB 필드에 대한 포인터, 현재 청크가 쓰여질 BLOB의 위치를 나타내는 오프셋 값 및 추가할 데이터 청크를 입력으로 사용합니다. 다음 코드 예제에서는 UPDATETEXT 함수에 대한 구문을 보여 줍니다. 여기서 @Pointer, @Offset,@BytesSqlCommand의 입력 매개 변수로 식별됩니다.

    UPDATETEXT Employees.Photo @Pointer @Offset 0 @Bytes

    오프셋 값은 응용 프로그램의 필요에 따라 사용자가 정하는 메모리 버퍼 크기에 의해 결정됩니다. 버퍼 크기가 크면 BLOB 쓰기 속도는 빠르지만 시스템 메모리가 더 많이 사용됩니다. 이 샘플에서는 비교적 적은 128바이트의 버퍼 크기를 사용합니다. 오프셋 값은 데이터의 첫 번째 청크에 대해 0부터 시작하여 각 연속 청크에 대해 버퍼 크기만큼 늘어납니다.

    이 예제의 경우 제시한 파일 경로에서 직원 사진을 청크 단위로 검색합니다. 각 청크를 지정된 버퍼 크기당 바이트 배열로 읽어옵니다. 그러면 바이트 배열은 SqlCommand@Bytes 입력 매개 변수 값으로 설정됩니다. 그런 다음 @Offset 매개 변수 값이 업데이트되고 SqlCommand가 실행되어 직원 레코드의 Photo 필드에 현재 청크 바이트가 추가됩니다.

    Visual Basic
    Imports System
    Imports System.Data
    Imports System.Data.SqlClient
    Imports System.IO
    
    Public Class EmployeeData
    
        Public Shared Sub Main()
          Dim hireDate As DateTime = DateTime.Parse("4/27/98")
          Dim newID As Integer = _
              AddEmployee("Smith", "John", "Sales Representative", hireDate, 5, "smith.bmp")
          Console.WriteLine("New Employee added. EmployeeID = " & newID)
        End Sub
    
        Public Shared Function AddEmployee(lastName As String, firstName As String, title As String, hireDate As DateTime, _
           reportsTo As Integer, photoFilePath As String) As Integer
    
        Dim connection As SqlConnection = New SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=Northwind;")
    
        Dim addEmp As SqlCommand = New SqlCommand("INSERT INTO Employees (LastName, FirstName, Title, HireDate, ReportsTo, Photo) " & _
          "Values(@LastName, @FirstName, @Title, @HireDate, @ReportsTo, 0x0);" & _
            "SELECT @Identity = SCOPE_IDENTITY();" & _
            "SELECT @Pointer = TEXTPTR(Photo) FROM Employees WHERE EmployeeID = @Identity", connection) 
    
        addEmp.Parameters.Add("@LastName", SqlDbType.NVarChar, 20).Value = lastName
        addEmp.Parameters.Add("@FirstName", SqlDbType.NVarChar, 10).Value = firstName
        addEmp.Parameters.Add("@Title", SqlDbType.NVarChar, 30).Value = title
        addEmp.Parameters.Add("@HireDate", SqlDbType.DateTime).Value     = hireDate
        addEmp.Parameters.Add("@ReportsTo", SqlDbType.Int).Value          = reportsTo
    
        Dim idParm As SqlParameter = addEmp.Parameters.Add("@Identity", SqlDbType.Int)
        idParm.Direction = ParameterDirection.Output
        Dim ptrParm As SqlParameter = addEmp.Parameters.Add("@Pointer", SqlDbType.Binary, 16)
        ptrParm.Direction = ParameterDirection.Output
    
        connection.Open()
    
        addEmp.ExecuteNonQuery()
    
        Dim newEmpID As Integer = CType(idParm.Value, Integer)
    
        StorePhoto(photoFilePath, ptrParm.Value, connection)
    
        connection.Close()
    
        Return newEmpID
      End Function
    
      Public Shared Sub StorePhoto(fileName As String, pointer As Byte(), connection As SqlConnection)
    
        Dim bufferLen As Integer = 128   ' The size of the "chunks" of the image.
    
        Dim appendToPhoto As SqlCommand = New SqlCommand("UPDATETEXT Employees.Photo @Pointer @Offset 0 @Bytes", connection)
    
        Dim ptrParm As SqlParameter = appendToPhoto.Parameters.Add("@Pointer", SqlDbType.Binary, 16)
        ptrParm.Value = pointer
        Dim photoParm As SqlParameter = appendToPhoto.Parameters.Add("@Bytes", SqlDbType.Image, bufferLen)
        Dim offsetParm As SqlParameter = appendToPhoto.Parameters.Add("@Offset", SqlDbType.Int)
        offsetParm.Value = 0
    
        ''''''''''''''''''''''''''''''''''''
        '' Read the image in and write it to the database 128 (bufferLen) bytes at a time.
        '' Tune bufferLen for best performance. Larger values write faster, but
        '' use more system resources.
    
    
        Dim fs As FileStream = New FileStream(fileName, FileMode.Open, FileAccess.Read)
        Dim br As BinaryReader = New BinaryReader(fs)
    
        Dim buffer() As Byte = br.ReadBytes(bufferLen)
        Dim offset_ctr As Integer = 0
    
        Do While buffer.Length > 0
          photoParm.Value = buffer
          appendToPhoto.ExecuteNonQuery()
          offset_ctr += bufferLen
          offsetParm.Value = offset_ctr
          buffer = br.ReadBytes(bufferLen)
        Loop
    
        br.Close()
        fs.Close()
      End Sub
    
    End Class
    
    using System;
    using System.Data;
    using System.Data.SqlClient;
    using System.IO;
    
    public class EmployeeData
    {
      public static void Main()
      {
        DateTime hireDate = DateTime.Parse("4/27/98");
        int newID  = AddEmployee("Smith", "John", "Sales Representative", hireDate, 5, "smith.bmp");
        Console.WriteLine("New Employee added. EmployeeID = " + newID);
      }
    
      public static int AddEmployee(string lastName, string firstName, string title, DateTime hireDate , int reportsTo, string photoFilePath)
      {
        SqlConnection connection = new SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=Northwind;");
    
        SqlCommand addEmp  = new SqlCommand("INSERT INTO Employees (LastName, FirstName, Title, HireDate, ReportsTo, Photo) " +
          "Values(@LastName, @FirstName, @Title, @HireDate, @ReportsTo, 0x0);" +
          "SELECT @Identity = SCOPE_IDENTITY();" +
          "SELECT @Pointer = TEXTPTR(Photo) FROM Employees WHERE EmployeeID = @Identity", connection);
    
        addEmp.Parameters.Add("@LastName",  SqlDbType.NVarChar, 20).Value = lastName;
        addEmp.Parameters.Add("@FirstName", SqlDbType.NVarChar, 10).Value = firstName;
        addEmp.Parameters.Add("@Title",     SqlDbType.NVarChar, 30).Value = title;
        addEmp.Parameters.Add("@HireDate",  SqlDbType.DateTime).Value = hireDate;
        addEmp.Parameters.Add("@ReportsTo", SqlDbType.Int).Value = reportsTo;
    
        SqlParameter idParm = addEmp.Parameters.Add("@Identity", SqlDbType.Int);
        idParm.Direction = ParameterDirection.Output;
        SqlParameter ptrParm = addEmp.Parameters.Add("@Pointer", SqlDbType.Binary, 16);
        ptrParm.Direction = ParameterDirection.Output;
    
        connection.Open();
    
        addEmp.ExecuteNonQuery();
    
        int newEmpID = (int)idParm.Value;
    
        StorePhoto(photoFilePath, (byte[])ptrParm.Value, connection);
    
        connection.Close();
    
        return newEmpID;
      }
    
      public static void StorePhoto(string fileName, byte[] pointer,  SqlConnection connection)
      {
        int bufferLen = 128;  // The size of the "chunks" of the image.
    
        SqlCommand appendToPhoto = new SqlCommand("UPDATETEXT Employees.Photo @Pointer @Offset 0 @Bytes", connection);
    
        SqlParameter ptrParm  = appendToPhoto.Parameters.Add("@Pointer", SqlDbType.Binary, 16);
        ptrParm.Value = pointer;
        SqlParameter photoParm = appendToPhoto.Parameters.Add("@Bytes", SqlDbType.Image, bufferLen);
        SqlParameter offsetParm = appendToPhoto.Parameters.Add("@Offset", SqlDbType.Int);
        offsetParm.Value = 0;
    
        //''''''''''''''''''''''''''''''''''
        // Read the image in and write it to the database 128 (bufferLen) bytes at a time.
        // Tune bufferLen for best performance. Larger values write faster, but
        // use more system resources.
    
        FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
        BinaryReader br = new BinaryReader(fs);
    
        byte[] buffer = br.ReadBytes(bufferLen);
        int offset_ctr = 0;
    
        while (buffer.Length > 0)
        {
          photoParm.Value = buffer;
          appendToPhoto.ExecuteNonQuery();
          offset_ctr += bufferLen;
          offsetParm.Value = offset_ctr;
          buffer = br.ReadBytes(bufferLen);
        }
    
        br.Close();
        fs.Close();
      }
    }
    

    참고 항목

    Posted by tornado
    |

    사용자가 생성한 쓰레드내 (사용자 쓰레드) 에서, 메인 쓰레드가 소유한 컨트롤 객체를 제어하기 위해서는 Invoke 를 통해 해결하셔야 합니다.


    간략히 이해하시려면 사용자 쓰레드에서 이벤트를 발생시켜서, 메인 쓰레드에서 이벤트로 받아 처리하라는 이야기 인데요. (이게 더 말이 어렵나??


    개발자는 머니머니해도 소스 몇줄 보여드리면 이해가 팍팍..


            // 버튼을 누르면 쓰레드 동작..

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

            {           

                hTread = new Thread(new ThreadStart(Test));

                hTread.Start();

            }


            private void Test()

            {

                try

                {

                    while(true)

                    {

                        // Form2 를 열기위한 이벤트 발생..

                        this.Invoke(new EventHandler(InvokeShowDialog));

                        Thread.Sleep(5000);

                    }

                }

                catch(Exception err)

                {

                    MessageBox.Show(err.ToString());

                }

               

            }

         

            // 이벤트 처리

            private void InvokeShowDialog(object sender, EventArgs e)

           {

                Form2 f = new Form2();

                f.Show();

           }

    Posted by tornado
    |

    예전에 자바로 겁나 헤메던거 무지무지 간단하게 끝나네 그랴~


    -- 출처 : http://www.codeproject.com/useritems/Audio_Player__with_Winmm.asp 


    Note: This is an unedited contribution. If this article is inappropriate, needs attention or copies someone else's work without reference then please Report this article.

    Sample Image - Audio_Player__with_Winmm.jpg

    Introduction

    After looking for a simple, yet free, mp3 player I deceided to make my own. I based myself on a commercial package that shall remain nameless [ Let's just say it that package was expensive considering it was importing the winmm.dll ]  Almost every example I found for playing audio file [in my case MP3s] using the winmm.dll only had play/stop capabilities (and sometimes pause). Which is fine, I just wanted a bit more, and still keep it simple. Of course, if I had wanted a full blown MP3 player, I propably would have download one of the many free application out there. That wasn't my purpose.
    So the code is fairly simple. It uses the winmm.dll from Windows to play,stop,pause etc. As well as control the volume of the left/right channels (if there are more then 1). Also, I had fun trying my hand at parsing XML, so the application gets audio file infomartion from a Windows Media Playlist [Making one is easy, just use Windows Media Player].

    Just please keep in mind, that I'm not a programmer by trade and this is my first contribution to the CodeProject. I know that I'm not re-inventing the wheel here, just hoping this will help someone somewhere.

    The project has a couple of files:
    -Player.cs [Which has all of the winmm.dll string commands]
    more can be found here http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_multimedia_command_strings.asp
    Which in my opinion, is a pain to understand sometimes.
    -readPlaylist.cs
    This opens up a open dialog window and helps you select a *.wpl file
    -MainPlayer.cs
    Basically the buttons and click events etc etc.

    Let's look at the Player.cs
    Tried putting as many relevant comments in the code as I code with out going over board like I'm doing writting this acticle.

    Collapse
    using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; namespace MP3player { class Player { private string Pcommand; private bool isOpen; [DllImport("winmm.dll")] private static extern long mciSendString(string strCommand, StringBuilder strReturn, int iReturnLength, int bla); /// <SUMMARY> /// Not much to conctruct here /// </SUMMARY> public Player() { } /// <SUMMARY> /// Stops currently playing audio file /// </SUMMARY> public void Close() { Pcommand = "close MediaFile"; mciSendString(Pcommand, null, 0, 0); isOpen = false; } /// <SUMMARY> /// Opens audio file to play /// </SUMMARY> /// This is the audio file's path and filename public void Open(string sFileName) { Pcommand = "open \"" + sFileName + "\" type mpegvideo alias MediaFile"; mciSendString(Pcommand, null, 0, 0); isOpen = true; } /// <SUMMARY> /// Plays selected audio file /// </SUMMARY> /// If True,audio file will repeat public void Play(bool loop) { if (isOpen) { Pcommand = "play MediaFile"; if (loop) Pcommand += " REPEAT"; mciSendString(Pcommand, null, 0, 0); } } /// <SUMMARY> /// Pauses currently playing audio file /// </SUMMARY> public void Pause() { Pcommand = "pause MediaFile"; mciSendString(Pcommand, null, 0, 0); } /// <SUMMARY> /// Returns the current status player: playing,paused,stopped etc. /// </SUMMARY> public string Status() { int i = 128; System.Text.StringBuilder stringBuilder = new System.Text.StringBuilder(i); mciSendString("status MediaFile mode", stringBuilder, i, 0); return stringBuilder.ToString(); } /// <SUMMARY> /// Get/Set Lelf Volume Factor /// </SUMMARY> public int LeftVolume { get { return 0; //Guess could be used to return Volume level: I don't need it } set { mciSendString(string.Concat("setaudio MediaFile left volume to ", value), null, 0, 0); } } /// <SUMMARY> /// Get/Set Right Volume Factor /// </SUMMARY> public int RightVolume { get { return 0; //Guess could be used to return Volume level: I don't need it } set { mciSendString(string.Concat("setaudio MediaFile right volume to ", value), null, 0, 0); } } /// <SUMMARY> /// Get/Set Main Volume Factor /// </SUMMARY> public int MasterVolume { get { return 0; //Guess could be used to return Volume level: I don't need it } set { mciSendString(string.Concat("setaudio MediaFile volume to ", value), null, 0, 0); } } /// <SUMMARY> /// Get/Set Bass Volume Factor /// </SUMMARY> public int Bass { get { return 0; } set { mciSendString(string.Concat("setaudio MediaFile bass to ", value), null, 0, 0); } } /// <SUMMARY> /// Get/Set Treble Volume Factor /// </SUMMARY> public int Treble { get { return 0; } set { mciSendString(string.Concat("setaudio MediaFile treble to ", value), null, 0, 0); } } } } 

     

    Now for reading the .wpl file to get MP3s [or whatever] to play
    readPlaylist.cs

    Collapse
    using System; using System.Collections; using System.Text; using System.Xml; namespace MP3player { class readPlaylist { private ArrayList name = new ArrayList(); private string m_xmlFile; /// <SUMMARY> /// The Windows Media Playlist Path xxx.wpl file /// </SUMMARY> public string playListPath { get { return m_xmlFile; } set { m_xmlFile = value; Makeplaylist(); } } /// <SUMMARY> /// Return an Arraylist of file found in Windows Media Playlist file /// </SUMMARY> public ArrayList PlayList { get { return name; } } /// <SUMMARY> /// Fills up an Arraylist with titles found in the Windows Media Playlist file. /// Using XmlTextReader /// </SUMMARY> private void Makeplaylist() { XmlTextReader readList = new XmlTextReader(m_xmlFile); while (readList.Read()) { if (readList.NodeType == XmlNodeType.Element) { if (readList.LocalName.Equals("media")) { name.Add(readList.GetAttribute(0).ToString()); } } } } } }

    Let's use the code now.
    Here's a part of the file with all the click event and so on.
    So here's the start, make sure you got all our references...

    MediaPlayer.cs

    Collapse
    using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace MP3player { public partial class MainPlayer : Form { ArrayList nowPlaylist = new ArrayList(); Player pl = new Player(); public MainPlayer() { InitializeComponent(); } private void btnOpen_Click(object sender, EventArgs e) { lstBoxPlayList.Items.Clear(); openPlaylistDialog.ShowDialog(); readPlaylist readList = new readPlaylist(); readList.playListPath = openPlaylistDialog.FileName; nowPlaylist = readList.PlayList; for (int x = 0; x < nowPlaylist.Count; x++) { lstBoxPlayList.Items.Add(nowPlaylist[x]); } lstBoxPlayList.SetSelected(0, true); } 


    The above code is pretty straight foward. The btnOpen_click event will open a OpenFileDialog window. Get the selected .wpl file and send it to the readPlaylist class, which will in turn parse through it return an Arraylist with all the file names and there paths. Once that done, loop it to display it's content [in this case a listbox]. Now you have all your files ready to be played, all that's needed is to select one and press play. Or doudle-click on it start it (I won't show that part here).

    To play the selected file in the listbox:

     private void btnPlay_Click(object sender, EventArgs e) { if (lstBoxPlayList.Items.Count > 0) { pl.Open(lstBoxPlayList.SelectedItem.ToString()); pl.Play(false); } } 

    A simple way of checking that a file is really selected before playing.
    Besides that, pretty simple. Of course, if you've never done this, it's not. It's called the learning process.
    As mentionned above, I think the comments in the code are pretty good. That and the MSDN link that's at the top... you should do fine.

    Another thing, also tried my hand at controlling the Bass/Treble. Don't know if it works, since my speaker system on my computer ain't great, and I'm hard of hearing [hence the reason my speaker system is crappy]. But if I read the MSDN right, it seems to work the same way as the volume control.

    Guess that's about it, hope this little example will help someone out.

    About loneferret


    Studied English Teaching as a second language at university here in montreal(you wouldn't know it the way I type would ya), after that did some networking course with a bit of programming. Mostly working as a network admin these days.
    I also like to write short stories, and draw..mostly black and white stuff...

    Click here to view loneferret's online profile.

    Posted by tornado
    |