Rally Software

  • 1.  C# download Attachments

    Posted Nov 05, 2014 03:28 PM
    Hello,

    I'm using .Net 4.0 and trying to download attachments.  I cannot find anywhere how I can do this. 

    Is there an example anywhere that can get me started?

    Thanks
    Philip


  • 2.  Re: C# download Attachments

    Posted Nov 05, 2014 04:23 PM


  • 3.  Re: C# download Attachments

    Posted Nov 05, 2014 06:26 PM
    Hi there, I'm actually using the v2.0.  Sorry I was not clear in my original message.  I did try that logic on the v2.0 and it did not work at all.


  • 4.  Re: C# download Attachments

    Posted Nov 05, 2014 06:39 PM
    Also, I'm willing to download the 1.43 but I cannot find the derpecated DLL anywhere.


  • 5.  Re: C# download Attachments

    Posted Nov 05, 2014 08:28 PM
    Hello The item you pointed me to is version 1.43. I cannot seem to find that DLL. I understand it’s no longer supported What I’ve been using is version 2.0, I have tried to use the code in that example and it fails. Also when I isse the command Request sRequest = new Request("attachments"); It always returns 0 results, even though I know there are attachments. Any help is greatly appreciated. 


  • 6.  Re: C# download Attachments

    Posted Nov 06, 2014 11:32 AM
    Hi Philip.
    I modified the code to work with v2.0.

    One main difference between 1.43 and v2.0 is in how collections are handled.
    In v2.0 we removed the ability to return child collections in the same response for performance reasons. In v2.0 fetching a collection will return an object with the count and the url from which to get the collection data.

    In the older versions of WS API certain fetch lists create a lot recursive calls, and all the collections included in the fetch make the call quite expensive. In WS API v2.0 this will not happen, since a separate call will have to be made in order to get objects of the collections. Please see WS API documentation (https://rally1.rallydev.com/slm/doc/webservice/rest_collections.jsp).

    Before we get to the code that works with v2.0, I wanted to mention that I was able to run the original 1.43 example and download an image attachement successfully without making any changes to the code (other than using my workspace/project refs, and query string to look for a story in my workspace) while using the latest dll 2.0.1 from here (https://github.com/RallyTools/RallyRestToolkitFor.NET).
    I did not change this line in the code:
     
    String wsapiVersion = "1.43";
    If I change this line to:
     
    String wsapiVersion = "v2.0";
    then I get an error
     
    Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: The
    best overloaded method match for 'Rally.RestApi.DynamicJsonObject.this[string]'
    has some invalid arguments
       at CallSite.Target(Closure , CallSite , Object , Int32 )
       at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site
    , T0 arg0, T1 arg1)
       at RestExample_DownloadAttachment.Program.Main(String[] args) in C:\Users\nick\Documents\Visual Studio 2010\Projects\DownloadAttachments\DownloadAttach
    ments\Program.cs:line 72
    If I set WS API version in this code example to v2.0 but the rest of the code is the same, the problem starts here:
    var myAttachmentFromStory = storyAttachments[0];

    Let's step back from the code and consider WS API json results that return Attachments in v2.0 and 1.43 respectively.
    This is the same story US20 I query in the code.

    When using v2.0 the Attachments object looks like this:
     
    Attachments: {
    _rallyAPIMajor: "2",
    _rallyAPIMinor: "0",
    _ref: "https://rally1.rallydev.com/slm/webservice/v2.0/HierarchicalRequirement/12525994836/Attachments",
    _type: "Attachment",
    Count: 3
    },


    When using 1.43 it looks like this:
     
    Attachments:     [
    {
    _rallyAPIMajor:     1
    _rallyAPIMinor:     43
    _ref:     https://rally1.rallydev.com/slm/webservice/1.43/attachment/12570906304.js
    _refObjectName:     AttachmentFromREST.png
    _type:     Attachment
    }
    {
    _rallyAPIMajor:     1
    _rallyAPIMinor:     43
    _ref:     https://rally1.rallydev.com/slm/webservice/1.43/attachment/15875922498.js
    _refObjectName:     AttachmentFromREST.png
    _type:     Attachment
    }
    {
    _rallyAPIMajor:     1
    _rallyAPIMinor:     43
    _ref:     https://rally1.rallydev.com/slm/webservice/1.43/attachment/15875900890.js
    _refObjectName:     AttachmentFromREST.png
    _type:     Attachment
    }
    Now we can see why storyAttachments[0] in the code works with 1.43 and does not work with v2.0. In v2.0 after we get the Attachments collection we need to make a separate request to hydrate it to get to all the elements of the collection.
    Here is the relevant part of the code example:
     
    Request attachmentsRequest = new Request(story["Attachments"]);
    QueryResult attachmentsResult = restApi.Query(attachmentsRequest);
    var myAttachmentFromStory = attachmentsResult.Results.First();
    The full code example is below. I have to mention that it is ouside of Rally support's scope to write and debug custom code and this is for illustration purposes only. We have Technical Services team (https://rallydev.force.com/answers?id=kA014000000PK1j) that does custom coding.

     
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.IO;
    using Rally.RestApi;
    using Rally.RestApi.Response;
    
    namespace DownloadAttachment
    {
        class Program
        {
            static void Main(string[] args)
            {
                RallyRestApi restApi;
    
                String userName = "user@co.com";
                String userPassword = "secret";
    
                String rallyURL = "https://rally1.rallydev.com";
                String wsapiVersion = "v2.0";
    
                restApi = new RallyRestApi(
                    userName,
                    userPassword,
                    rallyURL,
                    wsapiVersion
                );
    
                String workspaceRef = "/workspace/12352608129";
                String projectRef = "/project/12352608219";
                bool projectScopingUp = false;
                bool projectScopingDown = true;
    
                Request storyRequest = new Request("hierarchicalrequirement");
                storyRequest.Workspace = workspaceRef;
                storyRequest.Project = projectRef;
                storyRequest.ProjectScopeDown = projectScopingDown;
                storyRequest.ProjectScopeUp = projectScopingUp;
    
                storyRequest.Fetch = new List<string>()
                    {
                        "Name",
                        "FormattedID",
                        "Attachments"
                    };
    
                storyRequest.Query = new Query("FormattedID", Query.Operator.Equals, "US20");
                QueryResult queryResult = restApi.Query(storyRequest);
                DynamicJsonObject story = queryResult.Results.First();
    
                // Grab the Attachments collection
                Request attachmentsRequest = new Request(story["Attachments"]);
                QueryResult attachmentsResult = restApi.Query(attachmentsRequest);
              
                //Download the first attachment
    
                var myAttachmentFromStory = attachmentsResult.Results.First();
                String myAttachmentRef = myAttachmentFromStory["_ref"];
                Console.WriteLine("Found Attachment: " + myAttachmentRef);
    
                // Fetch fields for the Attachment
                string[] attachmentFetch = { "ObjectID", "Name", "Content", "ContentType", "Size" };
    
                // Now query for the attachment
                DynamicJsonObject attachmentObject = restApi.GetByReference(myAttachmentRef, "true");
    
                // Grab the AttachmentContent
                DynamicJsonObject attachmentContentFromAttachment = attachmentObject["Content"];
                String attachmentContentRef = attachmentContentFromAttachment["_ref"];
    
                // Lastly pull the content
                // Fetch fields for the Attachment
                string[] attachmentContentFetch = { "ObjectID", "Content" };
    
                // Now query for the attachment
                Console.WriteLine("Querying for Content...");
                DynamicJsonObject attachmentContentObject = restApi.GetByReference(attachmentContentRef, "true");
                Console.WriteLine("AttachmentContent: " + attachmentObject["_ref"]);
    
                String base64EncodedContent = attachmentContentObject["Content"];
    
                // File information
                String attachmentSavePath = "C:\\Users\\nmusaelian\\NewFolder";
                String attachmentFileName = attachmentObject["Name"];
                String fullAttachmentFile = attachmentSavePath + attachmentFileName;
    
                // Determine attachment Content mime-type
                String attachmentContentType = attachmentObject["ContentType"];
    
                // Specify Image format
                System.Drawing.Imaging.ImageFormat attachmentImageFormat;
    
                try
                {
                    attachmentImageFormat = getImageFormat(attachmentContentType);
                }
                catch (System.ArgumentException e)
                {
                    Console.WriteLine("Invalid attachment file format:" + e.StackTrace);
                }
    
                try
                {
    
                    // Convert base64 content to Image
                    Console.WriteLine("Converting base64 AttachmentContent String to Image.");
    
                    // Convert Base64 string to bytes
                    byte[] bytes = Convert.FromBase64String(base64EncodedContent);
    
                    Image myAttachmentImage;
                    using (MemoryStream ms = new MemoryStream(bytes))
                    {
                        myAttachmentImage = Image.FromStream(ms);
                        // Save the image
                        Console.WriteLine("Saving Image: " + fullAttachmentFile);
                        myAttachmentImage.Save(fullAttachmentFile, System.Drawing.Imaging.ImageFormat.Jpeg);
    
                        Console.WriteLine("Finished Saving Attachment: " + fullAttachmentFile);
                    }
    
                }
                catch (Exception e)
                {
                    Console.WriteLine("Unhandled exception occurred: " + e.StackTrace);
                    Console.WriteLine(e.Message);
                }
    
                Console.ReadKey();
            }
    
            // Returns an ImageFormat type based on Rally contentType / mime-type
            public static System.Drawing.Imaging.ImageFormat getImageFormat(String contentType)
            {
                // Save Image format
                System.Drawing.Imaging.ImageFormat attachmentImageFormat;
    
                switch (contentType)
                {
                    case "image/png":
                        attachmentImageFormat = System.Drawing.Imaging.ImageFormat.Png;
                        break;
                    case "image/jpeg":
                        attachmentImageFormat = System.Drawing.Imaging.ImageFormat.Jpeg;
                        break;
                    case "image/tiff":
                        attachmentImageFormat = System.Drawing.Imaging.ImageFormat.Tiff;
                        break;
                    default:
                        Console.WriteLine("Invalid image file format.");
                        throw new System.ArgumentException("Invalid attachment file format.");
                };
    
                return attachmentImageFormat;
            }
        }
    }