【问题标题】:Patching to TFS API with JSON gives 400 error (Bad Request)使用 JSON 修补 TFS API 会产生 400 错误(错误请求)
【发布时间】:2018-09-20 12:59:34
【问题描述】:

我正在尝试执行修补 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。

【问题讨论】:

  • 标题中“基本”后是否缺少空格字符?
  • 我刚试了,还是400错误。
  • 大部分时间的错误请求意味着您正在向服务器发送错误的内容,而不是身份验证。当然,“基本”之后应该有一个空格。您是否尝试过搜索文档以查看是否缺少某些内容?

标签: c# .net rest api tfs


【解决方案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;
}

【讨论】:

    【解决方案2】:

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

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

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

    试试如下:

    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;
    
                    }
    
                }
            }
        }
    }
    

    【讨论】:

    • @GeorgeCena 示例代码呢?这对你有用吗?还是同样会导致 400 错误?
    【解决方案3】:

    你也可以使用 nugate 包Microsoft.TeamFoundationServer.Client。

    从here 连接到团队项目:

    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;                    
    }
    

    我添加 cmets 的代码:

    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;  
    

    【讨论】:

    • 我一直在尝试这个,但无论如何我都无法让软件包工作。
    【解决方案4】:

    好的,伙计们,很遗憾,您的解决方案都不起作用,我认为这是因为 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;
    
                }
    
            }
    
        }
    

    【讨论】:

      猜你喜欢
      • 2015-08-19
      • 2018-11-20
      • 2022-01-04
      • 2018-05-10
      • 1970-01-01
      • 2020-11-13
      • 2011-06-18
      • 2017-02-13
      • 1970-01-01
      相关资源
      最近更新 更多