Share via


How to: Create Data Source Extensions for File-Based Import and Export

Note

With the release of ECMA 2.0, this feature has been deprecated and will be removed in future versions. You should use the Extensible Connectivity 2.0 Management Agent Reference for Connector development going forward.

This topic demonstrates how a file-based connected data source uses connected data source extensions to import and export objects.

When you create an extensible management agent in Synchronization Service Manager based on the sample code in this topic, you must have a file template to describe the schema of the import file to create the synchronization rules. The sample code in this topic creates an import file that has the following schema:

ObjectClass,Delta,Anchor-Attribute,Name,Email
Person,Add,1,Object1,Object1@fabrikam.com

You can use this schema as a template for creating a management agent. Copy it to a file using a text editor, save the file, and select it as the Template Input File in the sample code in this topic.

The following C# example shows how a file-based connected data source uses connected data source extensions to import and export objects. For more information about how this extension works, see Connected Data Source Extensions for File-Based Data Sources

using System;

using System.IO;

using System.Xml;

using System.Text;

using System.Collections.Specialized;

using Microsoft.MetadirectoryServices;

namespace SampleMAFileExport

{
    
    public class SampleMAFileExport : 
     
    
        IMAExtensibleFileImport, 
    
        IMAExtensibleFileExport
    {
        //
        // Constructor
        //
        public SampleMAFileExport()
        
        {
            
            m_xmlWriterExport   = null;
            
            m_encoding          = UnicodeEncoding.Unicode;
            
        }
         
        public void GenerateImportFile( 
        
            string                      filename, 
       
            string                      connectTo, 
       
            string                      user, 
       
            string                      password, 
       
            ConfigParameterCollection   configParameters,
       
            bool                        fullImport, 
       
            TypeDescriptionCollection   types,
       
            ref string                  customData 
       
            )
       
        {
            //
            // In the following sample, we read the XML file and generate
            // a comma-delimited XML file.
            //
            // NOTE: The connectTo, user, and password attributes in the XML file
            // are not used. However, if the connected data source
            // requires this connection information, these connection
            // parameters are passed into the method.
            // customData can be used for retaining necessary data
            // for performing a subsequent import. 
            // A typical case of this might be the watermark used 
            // for delta import.
            //
            
            SampleFile.GenerateSampleImportFile(
            
                configParameters,
                
                types,
                
                fullImport
                
                ? @"c:\sample_full_import.xml" 
                
                : @"c:\sample_delta_import.xml",
                
                filename,
                
                m_encoding
                
                );
                
        }

        //
        //  IMAExtensibleFileExport interface methods
        //

        
        public void DeliverExportFile(
        
            string                      fileName,
        
            string                      connectTo,
        
            string                      user,
        
            string                      password,
       
            ConfigParameterCollection   configParameters,
       
            TypeDescriptionCollection   types
       
            )
        
        {
            
            StreamReader    sr = new StreamReader(
            
                                            fileName, 
            
                                            m_encoding
            
                                                 );
                                                 
            string          lineContents = null;
            
            string          exportFile = null;
            
            exportFile = MAUtils.MAFolder.ToString() + @"\sample_export.xml";

            m_xmlWriterExport= new XmlTextWriter(
                                    
                                    exportFile, 
                                    
                                    m_encoding
                                                
                                                );

            m_xmlWriterExport.WriteStartElement(Nodes.Root);

            while (null != (lineContents = sr.ReadLine()))
            
            {
            
                char[]      commaEscape     = new char[] {','};
            
                char[]      quoteEscape     = new char[] {'"'};
            
                string[]    valueComponents = lineContents.Split(commaEscape);
                
                //
                // NOTE: In our sample, we assume that the order given to us is:
                //  objectclass, delta, anchor-attribute, name, e-mail.
                //

                //
                // Check the objectclass node and see if this object class is
                // something that we are interested in.
                //
                if (Nodes.ObjectClass == valueComponents[0].Trim(quoteEscape))
            
                {
            
                    continue;
            
                }

                //
                // This means that we are interested in this object class.
                // Populate the comma-delimited file.
                //
                m_xmlWriterExport.WriteStartElement(
                
                                                     Nodes.Object
                    
                                                    );

                m_xmlWriterExport.WriteElementString(
                    
                                                     Nodes.ObjectClass,
                                                     
                                                     valueComponents[0].Trim(quoteEscape)
                        
                                                    );

                m_xmlWriterExport.WriteElementString(
                    
                    Nodes.Delta,
                    
                    valueComponents[1].Trim(quoteEscape)
                    
                    );

                m_xmlWriterExport.WriteElementString(
                    
                    Nodes.Anchor,
                    
                    valueComponents[2].Trim(quoteEscape)
                    
                    );

                m_xmlWriterExport.WriteElementString(
                    
                    Nodes.Name,
                    
                    valueComponents[3].Trim(quoteEscape)
                    
                    );

                m_xmlWriterExport.WriteElementString(
                    
                    Nodes.Email, 
                    
                    valueComponents[4].Trim(quoteEscape)
                    
                    );

                
                m_xmlWriterExport.WriteEndElement();
                
            }

            m_xmlWriterExport.WriteEndElement();
            
            m_xmlWriterExport.Close();
        }

        //
        // Members
        //
        XmlTextWriter       m_xmlWriterExport;
        
        Encoding            m_encoding;
        
    }

    public class SampleFile
    {
        //
        // Class used to read XML and to mimic a connection to the 
        // connected directory.
        //
        
        /// <summary>
        /// This public function contains the logic to iterate through 
        /// each node of the incoming XML file.
        /// It then constructs the appropriate text file consumed by import.
        /// </summary>
        /// <param name="configParameters">Contains a 
        /// <see cref="ConfigParameterCollection"/> object that contains 
        /// a collection of <see cref="ConfigParameter"/> objects.</param>
        /// <param name="types">Contains a list of definitions for the object types</param>
        /// <param name="filenameXML">Incoming XML file to read from</param>
        /// <param name="filenameGeneratedImport">Name of delimited file to product</param>
        /// <param name="encoding">Encoding used for the file</param>
        public static void GenerateSampleImportFile(
        
            ConfigParameterCollection   configParameters,
            
            TypeDescriptionCollection   types,
            
            string                      filenameXML,
            
            string                      filenameGeneratedImport,
            
            System.Text.Encoding        encoding
            
            )
            
        {
            
            StreamWriter    swImport    = new StreamWriter(
                                                
                                                filenameGeneratedImport, 
                                                
                                                false, 
                                                
                                                encoding
                                                          
                                                          );
                                                          
            XmlDocument     doc         = new XmlDocument();
            
            XmlElement      root        = null;

            //
            // Read the sample XML file from the specified file above.
            //
            doc.Load(filenameXML);
                
            root = doc.DocumentElement;

            //
            // Write out the header of the delimited file.
            //
            swImport.WriteLine(
            
                "{0},{1},{2},{3},{4}",
            
                Nodes.ObjectClass,
            
                Nodes.Delta,
            
                Nodes.Anchor,
            
                Nodes.Name,
            
                Nodes.Email
            
                );
                
            foreach (XmlNode node in root.ChildNodes)
            {
            
                GenerateSampleImportFileForNode(
            
                    (XmlElement)node, 
            
                    configParameters, 
            
                    types, 
            
                    swImport
            
                    );
            
            }

            swImport.Close();
            
        }

        /// <summary>
        /// This private internal method contains the logic to generate the sample import file.
        /// </summary>
        /// <param name="elem">Contains the child XML element node</param>
        /// <param name="configParameters">Contains a 
        /// <see cref="ConfigParameterCollection"/> object that contains 
        /// a collection of <see cref="ConfigParameter"/> objects.</param>
        /// <param name="types">Contains a list of definitions for the object types</param>
        /// <param name="swImport">Contains a StreamWriter object to generate the 
        /// delimited import file</param>
        private static void GenerateSampleImportFileForNode(
            
            XmlElement                  elem,
            
            ConfigParameterCollection   configParameters,
            
            TypeDescriptionCollection   types,
            
            StreamWriter                swImport
            
            )
        
        {
            
            try
        
            {
            
                string              objectclassValue    = null;
            
                string              deltaValue          = null;
            
                string              nameValue           = null;
            
                string              emailValue          = null;
            
                string              anchorValue         = null;
            
                TypeDescription     typeDescription     = null;
                                
                //
                // In the following sample, we do not use the ConfigParameterCollection
                // class. However, if you wantto use it, you can do something similar  
                // to the following solution.
                //
                //  try
                //  {
                //      ConfigParameter configParam         = configParameters["Attrib"];
                //      string          configAttribValue   = configParam.Value;
                //  }
                //  catch (NoSuchParameterException)
                //  {
                //      ...
                //  }
                //
                foreach (XmlNode node in elem.ChildNodes)
                
                {
                    //
                    // NOTE: We assume that the XML is well-formed.
                    // The sample does not do any validation.
                    //
                    if (Nodes.ObjectClass == node.Name)
                    
                    {
                        //
                        // Check to see if the object class that we are 
                        // currently importing is something that we are 
                        // interested in.
                        // We do this by accessing the object class. If it is 
                        // not supported, an exception will be thrown.
                        // This is an example only and 
                        // is not mandatory.
                        //
                        typeDescription = types[node.InnerText];

                        objectclassValue = node.InnerText;
                    
                    }
                    
                    else if (Nodes.Delta == node.Name)
                    
                    {
                        //
                        // Delta node
                        //
                        deltaValue = node.InnerText;
                    
                    }
                    
                    else
                    
                    {
                        //
                        // As example, we want to process all 
                        // the attributes in the file. Some may not
                        // be interesting to use. We will skip those attributes
                        // via our try-catch block below.
                        // This is an example only and is
                        // not mandatory.
                        // 
                        // NOTE: We have made the assumption in this sample
                        // that the XML for the objectClass appears before 
                        // any attributes below.
                        // Otherwise, typeDescription would be null and an
                        // exception would be thrown.
                        //
                        try
                        {
                    
                            AttributeDescription    attribDescription;
                        
                            //
                            // Access each attribute to see if an exception will
                            // be thrown.
                            //
                    
                            attribDescription = 
                                typeDescription.Attributes[node.Name];

                            //
                            // Populate the proper attribute below
                            //
                            if (Nodes.Anchor == node.Name)
                    
                            {
                    
                                anchorValue = node.InnerText;
                    
                            }
                    
                            if (Nodes.Name == node.Name)
                    
                            {
                    
                                nameValue = node.InnerText;
                    
                            }
                    
                            else if (Nodes.Email == node.Name)
                    
                            {
                    
                                emailValue = node.InnerText;
                    
                            }
                    
                        }
                    
                        catch (NoSuchAttributeException)
                    
                        {
                            //
                            // Attribute is not defined and so we 
                            // do not need to process it.
                            //
                    
                        }
                    
                    }
                
                }
                
                swImport.WriteLine(
                
                    "{0},{1},{2},{3},{4}",
                
                    objectclassValue,
                
                    deltaValue,
                
                    anchorValue,
                
                    nameValue,
                
                    emailValue
                
                    );
                    
            }
            
            catch (NoSuchClassException)
            
            {
                //
                // Object class is not defined and so we do not need to process it.
                //
            
            }
            
        }
        
    }
    
    struct Nodes
    {
        //
        // Struct used to keep track of the XML node names.
        // This is used when generating the XML file.
        //

        public const string Root        = "sample-objects";

        public const string Object      = "object";

        public const string Anchor      = "anchor-attribute";

        public const string Delta       = "delta";

        public const string ObjectClass = "objectclass";

        public const string Name        = "name";            

        public const string Email       = "email";            

    }
    
}

The following Visual Basic example shows how a file-based connected data source uses connected data source extensions to import and export objects. For more information about how this extension works, see Connected Data Source Extensions for File-Based Data Sources.

Imports System

Imports System.IO

Imports System.Xml

Imports System.Text

Imports System.Collections.Specialized

Imports Microsoft.MetadirectoryServices

Public Class SampleMAFileExport

    Implements IMAExtensibleFileImport

    Implements IMAExtensibleFileExport

    Public Sub GenerateImportFile(ByVal fileName As String, _
                                  ByVal connectTo As String, _
                                  ByVal user As String, _
                                  ByVal password As String, _
                                  ByVal configParameters As ConfigParameterCollection, _
                                  ByVal fFullImport As Boolean, _
                                  ByVal types As TypeDescriptionCollection, _
                                  ByRef customData As String) _
                                  Implements IMAExtensibleFileImport.GenerateImportFile

        ' In the following sample,  we read the XML file and generate
        ' a comma-delimited XML file.
        '
        ' NOTE: The connectTo, user, and password attributes in the
        ' XML file are not used. However, if the connected data data source
        ' requires this connection information, these connection
        ' parameters are passed into the method.
        ' customData can be used for retaining necessary data
        ' for performing a subsequent import. 
        ' A typical case of this might be the watermark used 
        ' for delta import.
        '

        Dim importFile As String

        If fFullImport Then

            importFile = "c:\sample_full_import.xml"

        Else

            importFile = "c:\sample_delta_import.xml"

        End If

        SampleFile.GenerateSampleImportFile(configParameters, _
                                            types, _
                                            importFile, _
                                            fileName, m_encoding)

    End Sub

    Public Sub DeliverExportFile(ByVal fileName As String, _
                                 ByVal connectTo As String, _
                                 ByVal user As String, _
                                 ByVal password As String, _
                                 ByVal configParameters As ConfigParameterCollection, _
                                 ByVal types As TypeDescriptionCollection) _
                                 Implements IMAExtensibleFileExport.DeliverExportFile

        Dim sr As New StreamReader(fileName, m_encoding)

        Dim lineContents As String = Nothing

        Dim exportFile As String = Nothing

        exportFile = MAUtils.MAFolder.ToString() + "\sample_export.xml"

        m_xmlWriterExport = New XmlTextWriter(exportFile, m_encoding)

        m_xmlWriterExport.WriteStartElement(Nodes.Root)

        While (Not (lineContents = sr.ReadLine()))

            Dim commaEscape() As Char = {","c}

            Dim quoteEscape() As Char = {ControlChars.Quote}

            Dim valueComponents As String() = lineContents.Split(commaEscape)

            '
            ' NOTE: In our sample, we assume that the order given to us is:
            '  objectclass, delta, anchor-attribute, name, e-mail.
            '
            '
            ' Check the objectclass node and see if this object class is
            ' something that we are interested in.
            '
            If Nodes.ObjectClass = valueComponents(0).Trim(quoteEscape) Then

                GoTo ContinueWhile1

            End If

            '
            ' This means that we are interested in this object class.
            ' Populate the comma-delimited file.
            '
            m_xmlWriterExport.WriteStartElement(Nodes.Object)

            m_xmlWriterExport.WriteElementString(Nodes.ObjectClass, valueComponents(0).Trim(quoteEscape))

            m_xmlWriterExport.WriteElementString(Nodes.Delta, valueComponents(1).Trim(quoteEscape))

            m_xmlWriterExport.WriteElementString(Nodes.Anchor, valueComponents(2).Trim(quoteEscape))

            m_xmlWriterExport.WriteElementString(Nodes.Name, valueComponents(3).Trim(quoteEscape))

            m_xmlWriterExport.WriteElementString(Nodes.Email, valueComponents(4).Trim(quoteEscape))

            m_xmlWriterExport.WriteEndElement()

ContinueWhile1:

        End While

        m_xmlWriterExport.WriteEndElement()

        m_xmlWriterExport.Close()

    End Sub

    Private m_xmlWriterExport As XmlTextWriter

    Private m_encoding As Encoding

End Class

Public Class SampleFile

    '
    ' Class used to read XML and to mimic a connection to the 
    ' connected directory.
    '
    Public Shared Sub GenerateSampleImportFile(ByVal configParameters As ConfigParameterCollection, _
                                               ByVal types As TypeDescriptionCollection, _
                                               ByVal filenameXML As String, _
                                               ByVal filenameGeneratedImport As String, _
                                               ByVal encoding As System.Text.Encoding)

        Dim swImport As New StreamWriter(filenameGeneratedImport, False, encoding)

        Dim doc As New XmlDocument

        Dim root As XmlElement = Nothing

        '
        ' Read the sample XML file from the preceding specified file.
        '
        doc.Load(filenameXML)

        root = doc.DocumentElement

        '
        ' Write out the header of the delimited file.
        '
        swImport.WriteLine("{0},{1},{2},{3},{4}", Nodes.ObjectClass, Nodes.Delta, Nodes.Anchor, Nodes.Name, Nodes.Email)

        Dim node As XmlNode

        For Each node In root.ChildNodes

            GenerateSampleImportFileForNode(CType(node, XmlElement), configParameters, types, swImport)

        Next node

        swImport.Close()

    End Sub

    Private Shared Sub GenerateSampleImportFileForNode(ByVal elem As XmlElement, ByVal configParameters As ConfigParameterCollection, ByVal types As TypeDescriptionCollection, ByVal swImport As StreamWriter)

        Try

            Dim objectclassValue As String = Nothing

            Dim deltaValue As String = Nothing

            Dim nameValue As String = Nothing

            Dim emailValue As String = Nothing

            Dim anchorValue As String = Nothing

            Dim typeDescription As TypeDescription = Nothing

            '
            ' In the following sample, we do not use the ConfigParameterCollection
            ' class. However, if you want to use it, you can do something something
            ' similar to the following solution.
            '
            '  try
            '  {
            '      ConfigParameter configParam         = configParameters["Attrib"];
            '      string          configAttribValue   = configParam.Value;
            '  }
            '  catch (NoSuchParameterException)
            '  {
            '      ...
            '  }
            '
            Dim node As XmlNode

            For Each node In elem.ChildNodes

                '
                ' NOTE: We assume that the XML is well-formed.
                ' The sample does not do any validation.
                '
                If Nodes.ObjectClass = node.Name Then

                    '
                    ' Check to see if the object class that we are 
                    ' currently importing is something that we are 
                    ' interested in.
                    ' We do this by accessing the object class. If it is 
                    ' not supported, an exception will be thrown.
                    ' This is an example only and 
                    ' is not mandatory.
                    '
                    typeDescription = types(node.InnerText)

                    objectclassValue = node.InnerText

                ElseIf Nodes.Delta = node.Name Then

                    '
                    ' Delta node
                    '
                    deltaValue = node.InnerText

                Else
                    '
                    ' As an example, we want to process all 
                    ' the attributes in the file. Some may not
                    ' be interesting to use. We will skip those attributes
                    ' via our try-catch block below.
                    ' This is an example only and is
                    ' not mandatory.
                    ' 
                    ' NOTE: We have made the assumption in this sample
                    ' that the XML for the objectClass appears before 
                    ' any attributes below.
                    ' Otherwise, typeDescription would be null and an
                    ' exception would be thrown.
                    '
                    Try

                        Dim attribDescription As AttributeDescription

                        '
                        ' Access each attribute to see if an exception will
                        ' be thrown.
                        '
                        attribDescription = typeDescription.Attributes(node.Name)

                        '
                        ' Populate the proper attribute below.
                        '
                        If Nodes.Anchor = node.Name Then


                            anchorValue = node.InnerText
                        End If

                        If Nodes.Name = node.Name Then


                            nameValue = node.InnerText


                        ElseIf Nodes.Email = node.Name Then


                            emailValue = node.InnerText
                        End If


                    Catch
                        ' Attribute is not defined and so we 
                        ' do not need to process it.
                        '
                    End Try

                End If

            Next node

           swImport.WriteLine("{0},{1},{2},{3},{4}", _
                              objectclassValue, _
                              deltaValue, _
                              anchorValue, _
                              nameValue, _
                              emailValue)

        Catch

        ' Object class is not defined and so we do not need to process it.

        End Try

    End Sub

End Class

Module Nodes
    '
    ' Struct used to keep track of the XML node names.
    ' This is used when generating the XML file.
    '
    Public Const Root As String = "sample-objects"

    Public Const [Object] As String = "object"

    Public Const Anchor As String = "anchor-attribute"

    Public Const Delta As String = "delta"

    Public Const ObjectClass As String = "objectclass"

    Public Const Name As String = "name"

    Public Const Email As String = "email"

End Module 'Nodes 

See Also

Concepts

Connected Data Source Extensions for File-Based Data Sources
Best Practices for Data Source Extension Exports
Creating Connected Data Source Extensions
Connected Data Source Extensions for Call-Based Data Sources