1

I have been trying to set up a function to call the Jira Rest API with parameters to then be able to create an issue in Jira. To call the Rest API Jira provided a documentation which describes how to call the Rest API here. On the website, the CURL and the JSON are given. Here is the REST API request I tried to set up in C#:

curl -D- -u peno.ch:yourpassword -H "Content-Type: application/json" --data @foo.json https://jira-test.ch.*******.net/rest/api/latest/issue/

This is the foo.json payload:

{
    "fields": {
   "project":
   {
      "key": "FOO"
   },
   "summary": "Test the REST API",
   "issuetype": {
      "name": "Task"
   }
  }
 }

I have tried to Implement a HttpWebRequest to call the Rest API and also I have tried with a WebClient. None of them worked. I kind of understand the API but I think I haven't got the parameters right, I think I am doing something wrong there. Also on Google, I didn't find any solution.

I am getting an Internal error from Jira when executing the function below. (no specific information on the error)

public static void CreateJiraRequest(JiraApiObject jiraApiObject)
{
    string url = "https://jira-test.ch.*******.net/rest/api/latest/issue/";
    string user = "peno.ch";
    string password = "****";

    var client = new WebClient();        
    string data = JsonConvert.SerializeObject(jiraApiObject);
    client.Credentials = new System.Net.NetworkCredential(user, password);
    client.UploadStringTaskAsync(url, data);
}

This is my JiraApiObject which exactly translates to the Json payload shown above.

public class JiraApiObject
{
    public class Project
    {
        public string key { get; set; }
    }

    public class Issuetype
    {
        public string name { get; set; }
    }

    public class Fields
    {
        public Project project { get; set; }
        public Issuetype issuetype { get; set; }
        public string summary { get; set; }
    }

    public class RootObject
    {
        public Fields fields { get; set; }
    }
}

When I execute the CURL command on the console everything works I just couldn't figure out how to structure the WebClient or HttpWebRequest.

I find that many Jira users face this problem and there is not a single good solution I could find on the internet. I hope to find a solution and help others who have the same problem by raising this question.

Mrs KOODA
  • 149
  • 2
  • 13
  • Have you checked that the JSON you're generating is correct? Try outputting it a text file or to Console and read it (possibly also post it/ state that it is correct) – MindSwipe Feb 04 '19 at 09:12
  • I converted it an reviewed it, it is identical to the JSON payload I have shown. – Mrs KOODA Feb 04 '19 at 09:28
  • There are a *lot* of questions asking how to translate a `curl` command to .NET code. This isn't about JIRA. What does `doesn't` work mean, what is the error? What does that command line do? Does it perform a `POST`? A `PUT`? `--data` suggests a POST. – Panagiotis Kanavos Feb 04 '19 at 09:41
  • It's also using basic authentication. This means the username/password are sent in cleartext as part of a header. `Credentials` doesn't know *which* authentication scheme to use so it starts with the most secure one - Windows Authentication. As [this similar question](https://stackoverflow.com/questions/16044313/webclient-httpwebrequest-with-basic-authentication-returns-404-not-found-for-v) shows, basic is used only if Windows auth isnt' available. You need to format the Authentication header in code – Panagiotis Kanavos Feb 04 '19 at 09:44
  • Possible duplicate [Webclient / HttpWebRequest with Basic Authentication returns 404 not found for valid URL](https://stackoverflow.com/questions/16044313/webclient-httpwebrequest-with-basic-authentication-returns-404-not-found-for-v) – Panagiotis Kanavos Feb 04 '19 at 09:49
  • Possible duplicate [JIRA Rest API Login using C#](https://stackoverflow.com/questions/11869780/jira-rest-api-login-using-c-sharp) – Panagiotis Kanavos Feb 04 '19 at 09:50

2 Answers2

2

In general, its best practice (many a times necessary) that you explicitly specify the content type, HTTP method, etc whenever you make a REST API call. Also, I prefer using HttpWebRequest object to make REST API calls. Here is the re-factored code:

public static void CreateJiraRequest(Chat jiraApiObject)
{
    string url = "https://jira-test.ch.*******.net/rest/api/latest/issue/";
    string user = "peno.ch";
    string password = "****";

    var request = (HttpWebRequest)WebRequest.Create(url);
    request.Method = "POST";
    request.ContentType = "application/json";
    request.Credentials = new System.Net.NetworkCredential(user, password);

    string data = JsonConvert.SerializeObject(jiraApiObject);

    using (var webStream = request.GetRequestStream())
    using (var requestWriter = new StreamWriter(webStream, System.Text.Encoding.ASCII))
    {
        requestWriter.Write(data);
    }

    try
    {
        var webResponse = request.GetResponse();
        using (var responseReader = new StreamReader(webResponse.GetResponseStream()))
        {
            string response = responseReader.ReadToEnd();
            // Do what you need to do with the response here.
        }
    }
    catch (Exception ex)
    {
        // Handle your exception here
        throw ex;
    }
}

Also, ensure that the JSON structure after serialising JiraApiObject matches the required JSON structure of the API in question. For your convenience, you may want to consider using JsonObject and JsonProperty attributes on the classes and properties so that you name them the way the API expects.

Tanveer Yousuf
  • 386
  • 1
  • 3
  • 16
  • This isn't really different from the OP's code and I'd bet it will fail in the same way with an authentication error – Panagiotis Kanavos Feb 04 '19 at 09:44
  • WebClient uses HttpWebRequest underneath. Switching from the high-level to the low-level API won't fix any problems except by chance. This code doesn't use basic authentication explicitly for example and will probably run into the same problems – Panagiotis Kanavos Feb 04 '19 at 09:48
  • @PanagiotisKanavos I provided complete sample code and I happen to prefer HttpWebRequest as I clearly mentioned. I do not believe authentication was an issue as it would have returned HTTP 401 error. It was apparent from the question that he was getting Internal Server Error instead. Therefore, not setting the method explicitly to POST and content type to JSON would have been the most likely causes. – Tanveer Yousuf Feb 06 '19 at 09:12
  • Bad requests are 4xx, not server errors. `UploadStringTaskAsync` [does a POST](https://referencesource.microsoft.com/#system/net/System/Net/webclient.cs,858). Setting the credentials doesn't use basic authentication. The linked duplicates explain that basic authentication is *not* attempted until after a 401. That's how IIS works but JIRA may well decide to abort. The second linked question is specifically about JIRA and basic authentication and shows you need to set the basic authentication header explicitly – Panagiotis Kanavos Feb 06 '19 at 09:18
  • OK, I agree its my mistake that I overlooked the default HTTP method behaviour of UploadStringTaskAsync which is indeed POST and could not have been the cause. – Tanveer Yousuf Feb 06 '19 at 10:15
0

Following is your backend RESTapi code in C#

    public class JiraController : ApiController
    {
        public IJiraInterface repository = new JiraRepository();

        [Route("api/Jira/getTickets")]
        [HttpGet]
        [EnableCors(origins: "http://localhost:8080", headers: "*", methods: "*")]
        public Object getTickets(string startAt,string maxResults)
        {
            return repository.getTickets(startAt,maxResults);
        }

        [Route("api/Jira/createTicket")]
        [HttpPost]
        [EnableCors(origins: "http://localhost:8080", headers: "*", methods: "*")]
        public Object createTicket(IssueModel issueModelObject)
        {
            return repository.createTicket(issueModelObject);
        }
    }

This is how your interface class should look

public interface IJiraInterface
{
    Object getTickets(string startAt,string maxResults);
    Object createTicket(IssueModel issueObj);
}

This is how entity looks like

    public class IssueModel
    {
      #region Properties
      public string RequesterType { get; set; }
      public string Summary { get; set; }
      public string Description { get; set; }
      public string Email { get; set; }
      #endregion
    }

This is your repository class

public class JiraRepository : IJiraInterface
{
      public JiraRepository()
      {
      }
      public Object getTickets(string startAt, string maxResults)
      {
        string URL = "https://YOUR_SUB_DOMAIN.atlassian.net/rest/api/3/search?jql=project=PROJECT_NAME"+ 
            "&startAt="+ startAt + 
            "&maxResults=" + maxResults;
        
        HttpWebRequest httpWReq = (HttpWebRequest)WebRequest.Create(URL);
        httpWReq.PreAuthenticate = true;
        httpWReq.Headers.Add("Authorization", "Basic " + getAuthorization());
        httpWReq.Headers.Add("Access-Control-Allow-Origin", "*");
        

        HttpWebResponse response = (HttpWebResponse)httpWReq.GetResponse();
        StreamReader reader = new StreamReader(response.GetResponseStream());
        string responseString = reader.ReadToEnd();
        return JObject.Parse(responseString);
      }
      public Object createTicket(IssueModel issueObj)
      {
        string URL = "https://YOUR_SUB_DOMAIN.atlassian.net/rest/api/3/issue";

        HttpWebRequest httpWReq = (HttpWebRequest)WebRequest.Create(URL);
        httpWReq.ContentType = "application/json";
        httpWReq.PreAuthenticate = true;
        httpWReq.Method = "POST";
        using (var streamWriter = new StreamWriter(httpWReq.GetRequestStream()))
        {
            string json =
            "{\n  \"update\": {},\n  \"properties\" : [\n      {\n          \"key\":\"requestType\",\n          \"value\":\""+ issueObj.RequesterType + "\"\n      }\n  ],\n\n  \"fields\": {\n    \"summary\": \"" + issueObj.Summary + "\",\n    \"issuetype\": {\n      \"id\": \"10203\"\n    },\n    \"project\": {\n      \"id\": \"10512\"\n    },\n  \"description\": {\n      \"type\": \"doc\",\n      \"version\": 1,\n      \"content\": [\n        {\n          \"type\": \"paragraph\",\n          \"content\": [\n            {\n              \"text\": \"" + issueObj.Description + "\",\n              \"type\": \"text\"\n            }\n          ]\n        }\n      ]\n    },\n    \"priority\": {\n      \"id\": \"10000\"\n    }\n  }\n}";
            streamWriter.Write(json);
        }
        httpWReq.Headers.Add("Authorization", "Basic " + getAuthorization());
        httpWReq.Headers.Add("Access-Control-Allow-Origin", "*");

        try
        {
            HttpWebResponse response = (HttpWebResponse)httpWReq.GetResponse();
            StreamReader reader = new StreamReader(response.GetResponseStream());
            string responseString = reader.ReadToEnd();
            return JObject.Parse(responseString);
        }
        catch(Exception e)
        {
            return JObject.Parse(e.Message);
        }

      }
    
      public string getAuthorization()
      {
        var username = "YOUR_JIRA_USERNAME_KEY";
        var password = "YOUR_JIRA_PASSWORD_KEY";
        return Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(username + ":" + password));

      } 
}

For frontend gothrough the following link https://stackoverflow.com/a/70280761/4921412

  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 09 '21 at 20:33