使用JSON修补TFS API会产生400错误(错误请求)

时间:2018-04-10 19:30:10

标签: c# .net rest api tfs

我试图通过TFS REST API执行补丁http请求来更改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。

4 个答案:

答案 0 :(得分: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;
}

答案 1 :(得分:0)

A 400表示请求格式错误。换句话说,客户端发送到服务器的数据流不符合规则。

对于具有JSON有效负载的REST API,通常使用400来根据服务的API规范以某种方式指示JSON无效。

因此,问题是由JSON主体引起的。

试试如下:

public int NewWaySum(int a, int b) => a + b;

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

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

答案 2 :(得分:0)

您也可以使用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;                    
}

我的代码添加评论:

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;  

答案 3 :(得分: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;

            }

        }

    }