function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
BGrimesBGrimes 

listMetadata 101?

I'm working on a simple proof which uses the Metadata API.  As I understand it you login to the standard data API and set the Metadata svc session values then you can make calls.  Here's a sample of that (I have another wrapped assembly that handles all data calls).

internal MetaWrapper()
        {
            _svc = new sforce_meta.MetadataService();
            _wrapper = new MetadataWrapper();

            Main.sforce.LoginResult _lr = _wrapper.Login();
            _svc.SessionHeaderValue = new sforce_meta.SessionHeader {sessionId = _lr.sessionId};
            _svc.Url = _lr.serverUrl;
        }

That's all fine and dandy and I can see in my watch that I have a session URL and Id set. URL in this instance is :

Url    "https://cs3-api.salesforce.com/services/Soap/u/18.0/00DQ0000000Ahhk"

 

Now at this point I'm just trying to do a simple query on all custom objects to test the connectivity and whatnot. 

 

sforce_meta.ListMetadataQuery query = new  sforce_meta.ListMetadataQuery {folder = null, type = "CustomObject"};

sforce_meta.FileProperties[] prop = _svc.listMetadata(new sforce_meta.ListMetadataQuery[] {query}, 20);

 

Here's the error I always get:

 

 

[System.Web.Services.Protocols.SoapHeaderException]    {"No operation available for request {http://soap.sforce.com/2006/04/metadata}listMetadata"}

 

Am I missing something simple?  Do I need to do something different withthe URL returned from the data svc binding? I've tried using various other API versions as the second arg in the listMetadata() method, with the same results.

 

Any direction at all would be helpful.

 

-Bryan

Best Answer chosen by Admin (Salesforce Developers) 
SuperfellSuperfell

Here's a sample of getting the workflow file for the workflows on contact, basically it defines a package that contains workflow for contacts, and then fetches it, and writes it out to a zip file. There's no way currently to read the workflow directly in the response.

 

 

            meta.RetrieveRequest rr = new meta.RetrieveRequest();
            rr.apiVersion = 20.0;
            rr.unpackaged = new meta.Package();
            meta.PackageTypeMembers m = new meta.PackageTypeMembers();
            m.members = new String [] {"Contact"};
            m.name = "Workflow";
            rr.unpackaged.types = new meta.PackageTypeMembers[] { m }; 
            rr.singlePackage = true;
            meta.AsyncResult ar = metaSvc.retrieve(rr);
            while (!ar.done)
            {
                System.Threading.Thread.Sleep(500);
                ar = metaSvc.checkStatus(new String[] { ar.id })[0];
            }
          
            meta.RetrieveResult res = metaSvc.checkRetrieveStatus(ar.id);
            System.IO.FileStream fs = new System.IO.FileStream("c:\\wf.zip", System.IO.FileMode.Create);
            fs.Write(res.zipFile, 0, res.zipFile.Length);
            fs.Close();

 

 

All Answers

SuperfellSuperfell

You need to use the metadataServerUrl (or something similar) not serverUrl.

BGrimesBGrimes

Right on, I totally missed that one.  Cheers!

BGrimesBGrimes

Ok, so Simon yolu solved that problem and now I'm looking at what to do next.  I mean I can call listMetadata and give it "Workflows" and it returns the list.  Fine. 

 

What I'm looking to do it to "query" for a workflow to eventually get at the rules/criterias for that workflow.  For now I'll take being able to pull back a populated Workflow[] collection to then figure out what to do it.  But to get that collection I don't understand if I'm to use retrieve with a manifest file every time (I'm basing this off of the insane Java snippet in the API doc) or if it's anything close to being more intiutive than that.

 

Like:

RetrieveRequest retrieveRequest = new RetrieveRequest();
retrieveRequest.setApiVersion(18.0);

retrieveRequest.MAGICALLYISSETTOGIVEMEWORKFLOWS = Please;

 

RetrieveResult _result = _metaSVC.Retrieve(retrieveRequest);

 

I'm probably not making much sense, but honestly this API has me grasping at straws.  I don't want to iterate through a list of names to just print them, I want to get to Workflows, interate through them and acess the properties that I can see in the API doc.

 

Any help would be GREATLY appreciated.

- Bryan

 

SuperfellSuperfell

Here's a sample of getting the workflow file for the workflows on contact, basically it defines a package that contains workflow for contacts, and then fetches it, and writes it out to a zip file. There's no way currently to read the workflow directly in the response.

 

 

            meta.RetrieveRequest rr = new meta.RetrieveRequest();
            rr.apiVersion = 20.0;
            rr.unpackaged = new meta.Package();
            meta.PackageTypeMembers m = new meta.PackageTypeMembers();
            m.members = new String [] {"Contact"};
            m.name = "Workflow";
            rr.unpackaged.types = new meta.PackageTypeMembers[] { m }; 
            rr.singlePackage = true;
            meta.AsyncResult ar = metaSvc.retrieve(rr);
            while (!ar.done)
            {
                System.Threading.Thread.Sleep(500);
                ar = metaSvc.checkStatus(new String[] { ar.id })[0];
            }
          
            meta.RetrieveResult res = metaSvc.checkRetrieveStatus(ar.id);
            System.IO.FileStream fs = new System.IO.FileStream("c:\\wf.zip", System.IO.FileMode.Create);
            fs.Write(res.zipFile, 0, res.zipFile.Length);
            fs.Close();

 

 

This was selected as the best answer
BGrimesBGrimes

Thanks for that snippet.  So far was able to pull down the workflows via the API and then I got a zip file.  Opening that gave me what I needed there, now i'm writing the XML code that traverses the XML doc for a given workflow rule name and puts its contents into an object for use.  Any iade at all why this API is file based?  I get that it's handy at time (like with Eclipse) but other than that it really makes any work with it more than a beast.

 

FWIW this is what I'm working with on geting the values out of the certain rule:

 

XDocument xmlDoc = XDocument.Load(string.Format("c:\\Documents and Settings\\bryang\\Desktop\\workflows\\{0}.workflow", "Account"));
          
           const string xmlns = "http://soap.sforce.com/2006/04/metadata";
           var ruleAttributes = from x in xmlDoc.Descendants(XName.Get("rules", xmlns))
                                where x.Element(XName.Get("fullName", xmlns)).Value == "Update Renewal Survey Elig for New Client"
                                select new RuleDefinition()
                                           {
                                               FullName = (string)x.Element(XName.Get("fullName", xmlns)),
                                               Description = (string)x.Element(XName.Get("description", xmlns))
                                           };

  After this works my plan is to get the pdf pull retrival into some type of stream that I can read the workflows out of using something like the DotNetZip library.

 

Simon, thanks for your help on this.

 

BGrimesBGrimes

Right, for the sake of closure, below is a small console app I created that uses the DotNetZip lib along with our inhouse sforce DAL to retrieve workflow rules on an object, and then look for one specific workflow rule (setting a coupe of fields for testing).  This gets the zip from the RetrieveRequest and, in stream, converts it to an XDocument for Linq to XML...and all of that.  As is..

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using CSG.Salesforce.Metadata.MetaDefinition;
using Ionic.Zip;
using InternalDAL = CSG.Salesforce.DLL.DAO;
using System.IO;
using System.Xml;
using System.Xml.Linq;

namespace CSG.Salesforce.Metadata
{
    public class Rules : IEnumerable

    {
        private readonly List<RuleDefinition> enumerableList = new List<RuleDefinition>();

        #region Implementation of IEnumerable

        // need to make the list enumerable
        public IEnumerator<RuleDefinition> GetEnumerator()
        {
            return enumerableList.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        /// <summary>
        /// Add item to the list of items that are being used
        /// </summary>
        /// <param name="o"></param>
        public void Add(RuleDefinition a)
        {
            enumerableList.Add(a);
        }

        public int Length()
        {
            return enumerableList.Count;
        }
    }

    public class RuleDefinition
    {
        public string FullName { get; set; }
        public string Description { get; set; }
    }

    class Program
    {
        public static string ObjectName { get; set; }
        public static string RuleName { get; set; }

        private static InternalDAL.SalesforceDAO _sforce;

       static void Main(string[] args)
        {
           // set props
           ObjectName = "Account";
           RuleName = "Update Renewal Survey Elig for New Client";

           // login to the APIs
           var _svc = Login();

           // pull the results
           var _results = RetrieveMetaObject(_svc); 

           // now to the Zip work
           var _doc = ReadZipIntoXMLDoc(_results);

           // got the xml, now get me an object
           RuleDefinition rule = ParseXMLIntoObject(_doc, RuleName);

           Console.WriteLine(rule.FullName);
        }

       static MetaDefinition.MetadataService Login()
        {
            _sforce = new InternalDAL.SalesforceDAO();
            MetaDefinition.MetadataService _metaSvc = new MetaDefinition.MetadataService();

            CSG.sforce.LoginResult _lr = _sforce.Login();
            _metaSvc.SessionHeaderValue = new MetaDefinition.SessionHeader { sessionId = _lr.sessionId };
            _metaSvc.Url = _lr.metadataServerUrl;

            Console.WriteLine("Logged in as {0}", _lr.userInfo.userName);

           return _metaSvc;
        }

        static MetaDefinition.RetrieveResult RetrieveMetaObject(MetaDefinition.MetadataService metaSvc)
        {
            // create request
            MetaDefinition.RetrieveRequest _request = new MetaDefinition.RetrieveRequest {apiVersion = 18.0, unpackaged = new MetaDefinition.Package()};
            MetaDefinition.PackageTypeMembers _pkgMbrs = new MetaDefinition.PackageTypeMembers { name = "Workflow", members = new string[] { ObjectName } };
            _request.unpackaged.types = new MetaDefinition.PackageTypeMembers[] { _pkgMbrs };

            MetaDefinition.RetrieveResult _results = null;

            // retrieve start
            MetaDefinition.AsyncResult _asyncResult = metaSvc.retrieve(_request);
    
            while (!_asyncResult.done)
            {
                System.Threading.Thread.Sleep(3000);  // wait three seconds
                _asyncResult = metaSvc.checkStatus(new String[] { _asyncResult.id })[0];
            }

            if (_asyncResult.state == MetaDefinition.AsyncRequestState.Error)
                // error, for this quick app just console it.
                Console.WriteLine("{0} {1}", _asyncResult.statusCode, _asyncResult.message);
            else
            {
                // get everything...
                _results = metaSvc.checkRetrieveStatus(_asyncResult.id);
                if (_results.messages != null)
                {
                    // if there are messages, applicationexception them?
                } 
                else
                    return _results;
            }
            return null;
        }

        static XDocument ReadZipIntoXMLDoc(RetrieveResult retrieve)
        {
            // extract the zip from the reeults
            ZipFile zip = ZipFile.Read(retrieve.zipFile);

            foreach (ZipEntry file in zip)
            {
                if (file.FileName == String.Format("unpackaged/workflows/{0}.workflow", ObjectName))
                {
                    byte[] buffer = new byte[0];

                    using (Ionic.Zlib.CrcCalculatorStream s = file.OpenReader())
                    {
                        // buffer needs a length, just use the calced length.
                        buffer = new byte[s.Length];

                        int n, totalBytesRead = 0;
                        do
                        { n = s.Read(buffer, 0, buffer.Length);
                            totalBytesRead += n; } while (n > 0);
                        
                        // file checks...taken directly from the DotNetLib samples.
                        if (s.Crc != file.Crc)
                            throw new Exception(
                                string.Format("The Zip Entry failed the CRC Check. (0x{0:X8}!=0x{1:X8})", s.Crc,
                                              file.Crc));

                        if (totalBytesRead != file.UncompressedSize)
                            throw new Exception(
                                string.Format("We read an unexpected number of bytes. ({0}!={1})",
                                              totalBytesRead, file.UncompressedSize));
                    }

                    MemoryStream stream = new MemoryStream(buffer);
                    return LoadFromStream(stream);
                }
            }
            return null;
        }

        static RuleDefinition ParseXMLIntoObject(XContainer xmlDoc, string ruleName)
        {
            const string xmlns = "http://soap.sforce.com/2006/04/metadata";
            var ruleAttributes = from x in xmlDoc.Descendants(XName.Get("rules", xmlns))
                                 where x.Element(XName.Get("fullName", xmlns)).Value == ruleName
                                 select new RuleDefinition()
                                 {
                                     FullName = (string)x.Element(XName.Get("fullName", xmlns)),
                                     Description = (string)x.Element(XName.Get("description", xmlns))
                                 };

            RuleDefinition rule = new RuleDefinition();
            foreach (var e in ruleAttributes)
            {
                rule = e;
                Console.WriteLine(rule.FullName);
                Console.WriteLine(rule.Description);

            }
            return rule;
        }

        // helpers
        static String[] ListTarget(MetaDefinition.MetadataService metaSvc)
        {
            MetaDefinition.ListMetadataQuery q = new MetaDefinition.ListMetadataQuery();

            List<string> workflows = new List<string>();
            q.type = "Workflow";
            MetaDefinition.FileProperties[] _wkflws = metaSvc.listMetadata(new MetaDefinition.ListMetadataQuery[] { q }, 18);

            foreach (MetaDefinition.FileProperties rp in _wkflws)
            {
                Console.WriteLine("{0}", rp.fileName);
                workflows.Add(rp.fullName);
            }

            return workflows.ToArray();
        }

        static XDocument LoadFromStream(Stream stream)
        {
            using (XmlReader reader = XmlReader.Create(stream))
            {
                return XDocument.Load(reader);
            }
        }

    }

}

 

I hope this helps anyone out, and thanks for the pointers Simon.


Cheers.

- Bryan

dkadordkador

For what it's worth - the file-based nature of the metadata API can be a bit annoying when you only have to work with a single type of metadata (in your case, workflow).  But most people actually need to deal with multiple types of metadata, and some metadata isn't particularly suited to representation in XML (i.e. the body of an apex class).