使用 JSON 修补 TFS API 会出现 400 错误(错误请求)

问题描述 投票:0回答:4

我正在尝试执行修补 http 请求,以通过 TFS REST API 更改 TFS 中的字段之一。我尝试了多种方法,但总是出现 400 错误。这是我现在拥有的:

public void SetFieldValue(string value, string path, int id)
{
    var httpWebRequest = (HttpWebRequest)WebRequest.Create(PatchwebAPIUrl("wit/workitems", id.ToString()));
    httpWebRequest.ContentType = "application/json-patch+json";
    httpWebRequest.Method = "PATCH";
    httpWebRequest.Headers["Authorization"] = "Basic" + Base64authorizationToken();
    using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
    {
        string json = "[{\"op\":\"replace\"," +
                      $"\"path\":\"{path}\"," +
                      $"\"value\":\"{value}\"}}]";


        streamWriter.Write(JsonConvert.SerializeObject(json));
        streamWriter.Flush();
        streamWriter.Close();
    }

    var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
    using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
    {
        var result = streamReader.ReadToEnd();
    }

}

以及调用该方法的测试方法:

[TestMethod()]
public void setFieldValue()
{
    TFSWebAPIImplementation webAPI = new TFSWebAPIImplementation();
    webAPI.SetFieldValue("654321", "/fields/Custom.Tracking", 61949);
}

PatchwebAPIUrl("...")
方法很好,并且返回一个好的URL,当我导航到它时,我得到了我想要编辑的JSON数据。我不是 100% 相信路径变量,但它的使用方式与 Microsoft 提供的示例相同。授权有效,只是基于这样一个事实:当我搞乱它时,我会收到 401。

c# .net rest tfs
4个回答
1
投票

这是我的示例代码:

工作项类别:

public class WorkItemAtrr
{
    [JsonProperty("id")]
    public int id;
    [JsonProperty("rev")]
    public int rev;
    [JsonProperty("fields")]
    public Dictionary<string, string> fields;
    [JsonProperty("_links")]
    public Dictionary<string, Link> _links;
    [JsonProperty("relations")]
    public List<Relation> relations;
    [JsonProperty("url")]
    public string url;
}

public class Link
{
    [JsonProperty("href")]
    public string href;
}

public class Relation
{
    [JsonProperty("rel")]
    public string rel;
    [JsonProperty("url")]
    public string url;
    [JsonProperty("attributes")]
    public RelationAttribute attributes;
}

public class RelationAttribute
{
   [JsonProperty("comment")]
   public string comment = "";
   [JsonProperty("isLocked")]
   public bool isLocked;
}

新字段和更新字段的类:

public class NewField
{
    [JsonProperty("op")]
    public string op = "add";
    [JsonProperty("path")]
    public string path;
    [JsonProperty("value")]
    public object value;
}

异常类:

public class RestApiExceptionContainer
{
    [JsonProperty("id")]
    public int id;
    [JsonProperty("innerException")]
    public string innerException;
    [JsonProperty("message")]
    public string message;
    [JsonProperty("typeName")]
    public string typeName;
    [JsonProperty("typeKey")]
    public string typeKey;
    [JsonProperty("errorCode")]
    public int errorCode;
    [JsonProperty("evenId")]
    public int eventId;
}

更新工作项的方法:

private static WorkItemAtrr UpdateWorkItemRest()
{
    Dictionary<string, string> _fields = new Dictionary<string, string>();  

    _fields.Add("REFERENCE_NAME", "VALUE");

    var _updatedWi = UpdateWorkItem("ID", _fields).Result;
}

准备请求的方法:

public async Task<WorkItemAtrr> UpdatedWorkItem(int pId, Dictionary<String, String> pFields)
{
    //PATCH https://{instance}/DefaultCollection/_apis/wit/workitems/{id}?api-version={version}
    string _query_url = String.Format("https://YOUR_SERVER/DefaultCollection/_apis/wit/workitems/{id}?api-version=1.0", pId);

    List<Object> flds = new List<Object>();

    foreach (var _key in pFields.Keys)
        flds.Add(new NewField { op = "add", path = "/fields/" + _key, value = pFields[_key] });

    HttpResponseMessage _response = await DoRequest(_query_url, JsonConvert.SerializeObject(flds), ClientMethod.PATCH);

    return JsonConvert.DeserializeObject<WorkItemAtrr>(await ProcessResponse(_response));
}

通用请求方法:

private async Task<HttpResponseMessage> DoRequest(string pRequest, string pBody, ClientMethod pClientMethod)
{
    try
    {
        HttpClientHandler _httpclienthndlr = new HttpClientHandler();                

//update for your auth                 
        if (UseDefaultCredentials) _httpclienthndlr.Credentials = CredentialCache.DefaultCredentials;
        else if (TFSDomain == "") _httpclienthndlr.Credentials = new NetworkCredential(TFSUserName, TFSPassword);
        else _httpclienthndlr.Credentials = new NetworkCredential(TFSUserName, TFSPassword, TFSDomain);


        using (HttpClient _httpClient = new HttpClient(_httpclienthndlr))
        {
            switch (pClientMethod)
            {
                case ClientMethod.GET:
                    return await _httpClient.GetAsync(pRequest);

                case ClientMethod.POST:
                    return await _httpClient.PostAsync(pRequest, new StringContent(pBody, Encoding.UTF8, "application/json"));

                case ClientMethod.PATCH:
                    var _request = new HttpRequestMessage(new HttpMethod("PATCH"), pRequest);
                    _request.Content = new StringContent(pBody, Encoding.UTF8, "application/json-patch+json");                            
                    return await _httpClient.SendAsync(_request);

                default:
                    return null;
            }

        }
    }
    catch (Exception _ex)
    {
        throw new Exception("Http Request Error", _ex);
    }
}

通用响应方法:

public async Task<string> ProcessResponse(HttpResponseMessage pResponse)
{
    string _responseStr = "";

    if (pResponse != null)
    {
        if (pResponse.IsSuccessStatusCode)
            _responseStr = await pResponse.Content.ReadAsStringAsync();
        else
        {
            _responseStr = await pResponse.Content.ReadAsStringAsync();
            var _error = JsonConvert.DeserializeObject<RestApiExceptionContainer>(_responseStr);

            throw new RestApiException(_error);
        }
    }

    return _responseStr;
}

0
投票

400 表示请求格式错误。也就是说,客户端发送给服务器的数据流不符合规则。

对于具有 JSON 负载的 REST API,400 通常用于指示根据服务的 API 规范,JSON 在某种程度上无效。

所以,问题是由 JSON body 引起的。

只需尝试如下所示:

string json = "[{\"op\":\"replace\",\"path\":\"/fields/System.Title\",\"value\":\"Title\"}]";

您还可以使用下面的示例通过 REST API 使用 PATCH 方法更新字段,它对我有用:

using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Newtonsoft.Json;

namespace UpdateWorkItemFiled0411
{
    class Program
    {
        static void Main(string[] args)
        {
            string password = "xxxx";
            string credentials = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "username", password )));

            Object[] patchDocument = new Object[1];

            patchDocument[0] = new { op = "replace", path = "/relations/attributes/comment", value = "Adding traceability to dependencies" };


            using (var client = new HttpClient())
            {
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials);


                var patchValue = new StringContent(JsonConvert.SerializeObject(patchDocument), Encoding.UTF8, "application/json-patch+json");

                var method = new HttpMethod("PATCH");
                var request = new HttpRequestMessage(method, "http://server:8080/tfs/DefaultCollection/_apis/wit/workitems/21?api-version=1.0") { Content = patchValue };
                var response = client.SendAsync(request).Result;


                if (response.IsSuccessStatusCode)
                {
                    var result = response.Content.ReadAsStringAsync().Result;

                }

            }
        }
    }
}

0
投票

您也可以使用 nugate 包 Microsoft.TeamFoundationServer.Client

此处连接到团队项目:

using Microsoft.TeamFoundation.Core.WebApi;
using Microsoft.VisualStudio.Services.Common;

...

//create uri and VssBasicCredential variables
Uri uri = new Uri(url);
VssBasicCredential credentials = new VssBasicCredential("", personalAccessToken);

using (ProjectHttpClient projectHttpClient = new ProjectHttpClient(uri, credentials))
{
    IEnumerable<TeamProjectReference> projects = projectHttpClient.GetProjects().Result;                    
}

我添加评论的代码:

JsonPatchDocument PatchDocument = new JsonPatchDocument(); 

PatchDocument.Add( 
    new JsonPatchOperation() 
    { 
        Operation = Operation.Add, 
        Path = "/fields/System.History", 
        Value = "Changes from script" 
    } 
); 

VssCredentials Cred = new VssCredentials(true); 
WorkItemTrackingHttpClient WIClient = new WorkItemTrackingHttpClient(new Uri("http://YOUR_SERVER/tfs/DefaultCollection"), Cred); 
WorkItem result = WIClient.UpdateWorkItemAsync(PatchDocument, id).Result;  

0
投票

好吧,伙计们,不幸的是,你们的解决方案都不起作用,我认为这是因为 TFS API 不喜欢外面总是有一组额外的大括号。这是解决我的问题的解决方案:

public void SetFieldValue(string value, string path, int id)
    {
        using (var client = new HttpClient())
        {
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Base64authorizationToken());
            StringBuilder sb = new StringBuilder();
            StringWriter sw = new StringWriter(sb);
            using (JsonWriter writer = new JsonTextWriter(sw))
            {
                writer.Formatting = Formatting.Indented;
                writer.WriteStartArray(); // [

                writer.WriteStartObject(); // {
                writer.WritePropertyName("op"); // "Product:"
                writer.WriteValue("replace");
                writer.WritePropertyName("path");
                writer.WriteValue(path);
                writer.WritePropertyName("value");
                writer.WriteValue(value);
                writer.WriteEndObject(); //}
                writer.WriteEnd(); // ]

            }

            var method = new HttpMethod("PATCH");
            var request = new HttpRequestMessage(method, PatchwebAPIUrl("wit/workitems", id.ToString())) { Content =  new StringContent(sb.ToString().Trim(new char[] {'{','}'}), Encoding.UTF8, "application/json-patch+json") };
            var response = client.SendAsync(request).Result;


            if (response.IsSuccessStatusCode)
            {
                var result = response.Content.ReadAsStringAsync().Result;

            }

        }

    }
© www.soinside.com 2019 - 2024. All rights reserved.