Click here to Skip to main content
13,198,727 members (41,336 online)
Click here to Skip to main content
Add your own
alternative version

Stats

17K views
418 downloads
5 bookmarked
Posted 19 Oct 2014

Creating an Issue with Multiple Attachments in Jira using Rest API

, 19 Oct 2014
Rate this:
Please Sign up or sign in to vote.
This is an alternative for "Creating a Issue in Jira using Rest API". This is an upgraded version as it contains a way of sending attachments with the issue. This version doesn't use MSXML as it is not recommended by Microsoft.

Introduction

This tip presents another approach to create an issue in Atlassian Jira. Jira is an Issue and Project tracking software following Agile. The code and technique described in this tip can be used to programmatically connect to Jira and create an issue. It can be implemented inside an error handler module to automatically add issues to a Jira account when the program encounters an error. The error details are sent to Jira as Json and the inputs go into the respective fields under that project. This article also deals with sending an attachment along with the issue. Jira Rest API currently doesn't provide any way of sending the issue and the attachments together. What we need to do is that we have to send the issue first and parse the Json response to get the issue key. We can then use the issue key to send attachments to that issue.

Background

A basic idea about consuming a web service should be enough to understand the code presented in this tip. Since this is a console application, some idea about that is also needed. This tip is an alternative to the previous one. So the obvious question is that why doesn't this one use MSXML like the previous one. The reason is here. As explained in my previous article, obviously it works, but it is not recommended by Microsoft.

Using the Code

As you can see, the Jira class is the one that contains all the code. I will just browse through the rest as it is pretty straight-forward. In the class below, you just have to set the properties needed. The resource URL, the json string that contains the issue data, your jira credentials and the attachment filepaths.

Program.cs

namespace TryJira
{
    class Program
    {
        static void Main(string[] args)
        {
            Jira objJira = new Jira();
            objJira.Url = "YOUR_JIRA_URL";
            objJira.JsonString = @"{""fields""      :     {
                                    ""project""     :     {
                                    ""key""         :       ""YOUR_JIRA_PROJECT_KEY""},
                                    ""summary""     :       ""ISSUE_SUMMARY"",
                                    ""description"" :       ""ISSUE_DESCRIPTION"",
                                    ""issuetype""   :     { 
                                    ""name""        :       ""ISSUE_TYPE""}}}";
            objJira.UserName="YOUR_JIRA_USERNAME";
            objJira.Password="YOUR_JIRA_PASSWORD";
            objJira.filePaths = new List<string>() { "FILEPATH1","FILEPATH2" };
            objJira.AddJiraIssue();
        }
    }
}

Utility.cs

The class below contains one utility method as of now. The GetEncodedCredentials() method returns the base64 encoded credentials.

namespace TryJira
{
    class Utility
    {
        public static string GetEncodedCredentials(string UserName, string Password)
        {
            string mergedCredentials = String.Format("{0}:{1}", UserName, Password);
            byte[] byteCredentials = Encoding.UTF8.GetBytes(mergedCredentials);
            return Convert.ToBase64String(byteCredentials);
        }
    }
}

Jira.cs

This class contains three methods and the properties. Let's skip the properties and go the first method.

namespace TryJira
{
    class Jira
    {
        public string UserName { get; set; }
        public string Password { get; set; }
        public string Url { get; set; }
        public string JsonString { get; set; }
        public IEnumerable<string> filePaths { get; set; }
public void AddJiraIssue()

This method adds the issue to the project. Once we open the connection via POST, we set the necessary headers. The content-type is set to json because we will be sending the project parameters as a json string. Next we set the authorization header and the credentials for basic HTTP authentication. We then convert the json string to bytes and write it to the request stream.

public void AddJiraIssue()
{
    string restUrl = String.Format("{0}rest/api/2/issue/", Url);
    HttpWebResponse response = null;
    HttpWebRequest request = WebRequest.Create(restUrl) as HttpWebRequest;
    request.Method = "POST";
    request.Accept = "application/json";
    request.ContentType = "application/json";
    request.Headers.Add("Authorization", "Basic " +
    Utility.GetEncodedCredentials(UserName,Password));
    byte[] data = Encoding.UTF8.GetBytes(JsonString);
    using (var requestStream = request.GetRequestStream())
    {
        requestStream.Write(data, 0, data.Length);
        requestStream.Close();
    }

Once the request is complete, we display the response on the console. Now, the response is in json. It contains the issue key and the link to the issue. We need the issue key to send attachments against that issue. We use the JavaScriptSerializer Class in System.Web.Script.Serialization to dereserialize the json response. A reference to System.Web.Extensions has to be added to the project solution for this. We use the deserialize method in the class to parse the json string and get a dictionary containing the objects. Like I said, we only need one, that is the issue key. Once we get the key, we call the AddAttachments() method, sending the key and the list of attachments as parameters.

The Json response after sending the issue looks like this:

{"id":"10111","key":"TP-59","self":
"https://YOUR_JIRA_URL/rest/api/2/issue/10111"}

Like I said, we need only the key, which is "TP-59" in this case because we will be sending attachments against that issue. The other two fields are ignored, for now. All other details for the issue can be found at the URL provided in the "self" field. The issue data will be available in Json format.

    using (response = request.GetResponse() as HttpWebResponse)
    {
        if (response.StatusCode != HttpStatusCode.OK)
        {
            var reader = new StreamReader(response.GetResponseStream());
            string str = reader.ReadToEnd();
            Console.WriteLine("The server returned '{0}'\n{1}", response.StatusCode, str);
            var jss = new System.Web.Script.Serialization.JavaScriptSerializer();
            var sData = jss.Deserialize<Dictionary<string, string>>(str);
            string issueKey = sData["key"].ToString();

            AddAttachments(issueKey, filePaths);
        }
    }
    request.Abort();
}
public void AddAttachments(string issueKey,IEnumerable<string> filePaths)

Well, now that we have the issue key, we simply need the attachments. Here, we simply check if the files to be attached are available and send the list of filepaths to the PostFile() method.

public void AddAttachments(string issueKey,IEnumerable<string> filePaths)
{
    string restUrl = String.Format("{0}rest/api/2/issue/{1}/attachments", Url, issueKey);
    var filesToUpload = new List<FileInfo>();
    foreach (var filePath in filePaths)
    {
        if (!File.Exists(filePath))
        {
            Console.WriteLine("File '{0}' doesn't exist", filePath);
        }
        var file = new FileInfo(filePath);
        filesToUpload.Add(file);
    }
    if (filesToUpload.Count <= 0)
    {
        Console.WriteLine("No file to Upload");
    }
    PostFile(restUrl,filesToUpload);
}

You should consider reading the Jira Rest API documentation for the next part. It clearly states that the Jira resource expects a multipart post. That is what the next method does. It handles the multipart posting. Now, to separate the parts, we must use a boundary which is not a part of the original data. Using a globally unique identifier seems to be the easiest way. Now we have to create a stream containing the data in the files to be sent. We create a memory stream object for that and use a stream writer object to write to the stream. The documentation clearly states that name of the multipart/form-data parameter that contains attachments must be "file". So we set the content disposition and the content type. After writing the contents of one file to the stream, we repeat the procedure for all the other files. I have kept the content-type to application/octet stream so that we can attach any type of file. In my case, I had to send one image(.png) and one XML file.

private void PostFile(string restUrl,IEnumerable<FileInfo> filePaths)
{
    HttpWebResponse response = null;
    HttpWebRequest request = null;
    String boundary = String.Format("----------{0:N}", Guid.NewGuid());
    var stream = new MemoryStream();
    var writer = new StreamWriter(stream);
    foreach (var filePath in filePaths)
    {
        var fs = new FileStream(filePath.FullName, FileMode.Open, FileAccess.Read);
        var data = new byte[fs.Length];
        fs.Read(data, 0, data.Length);
        fs.Close();
        writer.WriteLine("--{0}", boundary);
        writer.WriteLine("Content-Disposition: form-data; name=\"file\"; filename=\"{0}\"", filePath.Name);
        writer.WriteLine("Content-Type: application/octet-stream");
        writer.WriteLine();
        writer.Flush();
        stream.Write(data, 0, data.Length);
        writer.WriteLine();
    }

We then insert the boundary, flush the writer and set the stream position to 0. The next part is exactly similar like sending the issue, which has been described above. The only addition is the "X-Atlassian-Token" header which has been set to "nocheck". This is needed because the method has XSRF protection. Omitting this header will simply block the request.

            writer.WriteLine("--" + boundary + "--");
            writer.Flush();
            stream.Seek(0, SeekOrigin.Begin);
            request = WebRequest.Create(restUrl) as HttpWebRequest;
            request.Method = "POST";
            request.ContentType = string.Format("multipart/form-data; boundary={0}", boundary);
            request.Accept = "application/json";
            request.Headers.Add("Authorization", "Basic " + Utility.GetEncodedCredentials(UserName, Password));
            request.Headers.Add("X-Atlassian-Token", "nocheck");
            request.ContentLength = stream.Length;
            using (Stream requestStream = request.GetRequestStream())
            {
                stream.WriteTo(requestStream);
                requestStream.Close();
            }
            using (response = request.GetResponse() as HttpWebResponse)
            {
                if (response.StatusCode != HttpStatusCode.OK)
                {
                    var reader = new StreamReader(response.GetResponseStream());
                    Console.WriteLine("The server returned '{0}'\n{1}", response.StatusCode, reader.ReadToEnd());
                }
            }
            request.Abort();
        }
    }
}

Screen of the Jira Environment

This is how the issue looks like in the Jira project that I had created for testing purposes. Best of luck!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Abhijit Ghosh (Subho)
Software Developer (Junior) ICRA Online Limited
India India
About Me:
I am a junior software developer who is still learning how to write code that won't give others a headache. I love biking and travelling. I am also a big foodie and a cinephile.

Coding Style:
I am not a ninja programmer and I don't want to be one. I am the slowest developer you may ever encounter. I take a lot of time to develop code and I spend even more time reviewing it. My codes are always full of comments and summaries. Sometimes there are more comments than actual lines of code.

Pet Peeve:
A great software with zero or very little documentation. That is simply a great idea wasted, not to mention the time and labor of the dev team. Normally I wouldn't care unless I have to maintain it.

Liberal Views:
Free sharing of knowledge by the developer community has always amazed me. I do what I can to contribute. I believe that the 'end-user' rules. because his disturbingly feeble-minded needs drive me to build something better.

Egoistic Views:
I take pride in what I develop and the way I do it but I still bow down to all who can provide a better solution.

You may also be interested in...

Pro
Pro

Comments and Discussions

 
QuestionGreat article Abhijit !! Pin
leo.cba20-Dec-16 5:21
memberleo.cba20-Dec-16 5:21 
QuestionNice article Abhijit. Can you please guide me about reading issues? Pin
omairkhan808-Oct-15 2:13
memberomairkhan808-Oct-15 2:13 
GeneralHI Ghosh,It worked perfect;y fine .Its very good post Thanks a lot Njoyed ur post.:) Pin
Member 1159552113-Apr-15 0:48
memberMember 1159552113-Apr-15 0:48 
AnswerComments on a Issue Pin
Member 114208452-Feb-15 3:39
memberMember 114208452-Feb-15 3:39 
QuestionStatus of a JIRA Issue? Pin
Member 1140459526-Jan-15 20:00
memberMember 1140459526-Jan-15 20:00 
AnswerRe: Status of a JIRA Issue? Pin
Subho Ghosh29-Jan-15 2:44
professionalSubho Ghosh29-Jan-15 2:44 
AnswerRe: Status of a JIRA Issue? Pin
vijay prasad11-Dec-16 7:02
membervijay prasad11-Dec-16 7:02 
Question5! Pin
Volynsky Alex19-Oct-14 12:52
professionalVolynsky Alex19-Oct-14 12:52 
AnswerRe: 5! Pin
Abhijit Ghosh (Subho)19-Oct-14 20:40
professionalAbhijit Ghosh (Subho)19-Oct-14 20:40 
QuestionRe: 5! Pin
Volynsky Alex21-Oct-14 10:18
professionalVolynsky Alex21-Oct-14 10:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.171020.1 | Last Updated 19 Oct 2014
Article Copyright 2014 by Abhijit Ghosh (Subho)
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid